探索前沿技术
      展示技术风采

SpringBoot实战: SpringCache集成redis

springboot实战开发Spring Cache操作Redis

Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 Redis),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

特点

具备相当的好的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache、Redis、Guava 的集成。

  • 基于 annotation 即可使得现有代码支持缓存
  • 开箱即用 Out-Of-The-Box,不用安装和部署额外第三方组件即可使用缓存
  • 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
  • 支持 AspectJ,并通过其实现任何方法的缓存支持
  • 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性

使用前后

下面针对Spring Cache使用前后给出了伪代码部分,具体中也许比这要更加复杂,但是Spring Cache都可以很好的应对

使用前

我们需要硬编码,如果切换Cache Client还需要修改代码,耦合度高,不易于维护

public String get(String key) {
    String value = userMapper.selectById(key);
    if (value != null) {
        cache.put(key,value);
    }
    return value;
}

使用后

基于Spring Cache注解,缓存由开发者自己配置,但不用参与到具体编码

@Cacheable(value = "user", key = "#key")
public String get(String key) {
    return userMapper.selectById(key);
}

添加依赖

在 pom.xml 中添加 spring-boot-starter-data-redis的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

属性配置

在 application.yaml 文件中配置如下内容,由于Spring Boot2.x 的改动,连接池相关配置需要通过spring.redis.lettuce.pool 或者 spring.redis.jedis.pool 进行配置了。使用了Spring Cache后,能指定spring.cache.type就手动指定一下,虽然它会自动去适配已有Cache的依赖,但先后顺序会对Redis使用有影响JCache -> EhCache -> Redis -> Guava

spring:
  redis:
    host: 127.0.0.1
    timeout: 10000ms
    database: 0
    lettuce:
      pool:
        max-wait: -1ms
        max-active: 8
        max-idle: 8
        min-idle: 0
  cache:
    type: redis

具体编码

实体类

创建一个User类,目的是为了模拟对象存储

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/1 下午2:48
 * @since 1.0.0
 */
@Data
@AllArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 1774445780486455488L;

    private Long id;
    private String name;
    private String password;

}

定义接口

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/1 下午5:58
 * @since 1.0.0
 */
public interface UserService {

    /**
     * 添加或更新用户
     * @param user
     * @return
     */
    User saveOrUpdate(User user);

    /**
     * 根据🆔获取用户
     * @param id
     * @return
     */
    User get(Long id);

    /**
     * 根据🆔删除用户
     * @param id
     */
    void delete(Long id);
}

###实现类

为了方便演示数据库操作,直接定义了一个Map<Long, User> DATABASES,这里的核心就是@Cacheable@CachePut@CacheEvict 三个注解

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/1 下午6:00
 * @since 1.0.0
 */
@Service
@Log4j2
public class UserServiceImpl implements UserService {

    private static final Map<Long, User> DATABASES = new HashMap<>();

    static {
        DATABASES.put(1L, new User(1L, "u1", "p1"));
        DATABASES.put(2L, new User(2L, "u2", "p2"));
        DATABASES.put(3L, new User(3L, "u3", "p3"));
    }

    @Cacheable(value = "user",key = "#user.id")
    @Override
    public User saveOrUpdate(User user) {
        DATABASES.put(user.getId(), user);
        log.info("进入 saveOrUpdate 方法");
        return user;
    }

    @Cacheable(value = "user",key = "#id")
    @Override
    public User get(Long id) {
        log.info("进入 get 方法");
        return DATABASES.get(id);
    }

    @Cacheable(value = "user",key = "#id")
    @Override
    public void delete(Long id) {
        DATABASES.remove(id);
        log.info("进入 delete 方法");
    }
}

主函数

@EnableCaching 必须要加,否则spring-data-cache相关注解不会生效…

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/1 下午6:00
 * @since 1.0.0
 */
@SpringBootApplication
@EnableCaching
public class Drunk8Application {

    public static void main(String[] args) {
        SpringApplication.run(Drunk8Application.class, args);
    }
}

测试

完成准备事项后,编写一个junit测试类来检验代码的正确性,有很多人质疑过Redis线程安全性,故下面也提供了响应的测试案例,如有疑问欢迎指正

@RunWith(SpringRunner.class)
@SpringBootTest
@Log4j2
public class Drunk8ApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    public void contextLoads() {
        final User user = userService.saveOrUpdate(new User(5L, "u5", "p5"));
        log.info("[saveOrUpdate] - [{}]", user);
        final User user1 = userService.get(5L);
        log.info("[get] - [{}]", user1);
        userService.delete(5L);
    }

}

启动测试类,结果和我们期望的一致,可以看到增删改查中,查询是没有日志输出的,因为它直接从缓存中获取的数据,而添加、修改、删除都是会进入方法内执行具体的业务代码,然后通过切面去删除掉Redis中的缓存数据。其中 # 号代表这是一个 SpEL 表达式,此表达式可以遍历方法的参数对象,具体语法可以参考 Spring 的相关文档手册。

2018-07-02 19:34:14.223  INFO 940 --- [           main] c.f.b.d.service.impl.UserServiceImpl     : 进入 saveOrUpdate 方法
2018-07-02 19:34:14.231  INFO 940 --- [           main] c.f.blog.drunk8.Drunk8ApplicationTests   : [saveOrUpdate] - [User(id=5, name=u5, password=p5)]
2018-07-02 19:34:14.237  INFO 940 --- [           main] c.f.blog.drunk8.Drunk8ApplicationTests   : [get] - [User(id=5, name=u5, password=p5)]

其它类型

下列的就是Redis其它类型所对应的操作方式

  • opsForValue: 对应 String(字符串)
  • opsForZSet: 对应 ZSet(有序集合)
  • opsForHash: 对应 Hash(哈希)
  • opsForList: 对应 List(列表)
  • opsForSet: 对应 Set(集合)
  • opsForGeo: 对应 GEO(地理位置)

根据条件操作缓存

根据条件操作缓存内容并不影响数据库操作,条件表达式返回一个布尔值,true/false,当条件为true,则进行缓存操作,否则直接调用方法执行的返回结果。

  • 长度 @CachePut(value = "user", key = "#user.id",condition = "#user.username.length() < 10") 只缓存用户名长度少于10的数据
  • 大小 @Cacheable(value = "user", key = "#id",condition = "#id < 10") 只缓存ID小于10的数据
  • 组合 @Cacheable(value="user",key="#user.username.concat(##user.password)")
  • 提前操作: @CacheEvict(value="user",allEntries=true,beforeInvocation=true) 加上beforeInvocation=true后,不管内部是否报错,缓存都将被清除,默认情况为false

注解介绍

@Cacheable(根据方法的请求参数对其结果进行缓存)

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName")
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")

@CachePut(根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用)

  • key: 同上
  • value: 同上
  • condition: 同上

@CachEvict(根据条件对缓存进行清空)

  • key: 同上
  • value: 同上
  • condition: 同上
  • allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:@CacheEvict(value = "user", key = "#id", allEntries = true)
  • beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:@CacheEvict(value = "user", key = "#id", beforeInvocation = true)
springboot实战开发

springboot实战开发

×

感谢您的支持,我们会一直保持!

扫码支持
请土豪扫码随意打赏

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

赞(0) 打赏
未经允许不得转载:醉探索戈壁 » SpringBoot实战: SpringCache集成redis
分享到: 更多 (0)
标签:

给戈壁浇点水

支付宝扫一扫打赏

微信扫一扫打赏