【Spring Boot】Redis缓存

1. 前言

本文主要介绍在Spring Boot项目中整合Redis缓存,了解其基础用法。

2. 引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 对象池,使用 redis 时必须引入 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- 引入 jackson 对象json转换 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
</dependency>

为什么要引入commons-pool2呢?因为不管是Jedis还是lettuce都有用到它。

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

class LettuceConnectionConfiguration extends RedisConnectionConfiguration

private GenericObjectPoolConfig<?> getPoolConfig(Pool properties)

3. 配置

3.1 application.yml

使用lettuce作为连接Redis Server的客户端。

spring:
  redis:
    host: localhost
    # 连接超时时间(记得添加单位,Duration)
    timeout: 10000ms
    # Redis默认情况下有16个分片,这里配置具体使用的分片
    # database: 0
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1ms
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0
  cache:
    # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配
    type: redis

3.2 RedisConfig

@AutoConfigureAfter(RedisAutoConfiguration.class) 表示先加载RedisAutoConfiguration类,后加载RedisConfig类。

@AutoConfigureAfter就是在加载指定类之后,再加载当前类。

RedisTemplateSpring提供用来方便操作Redis缓存的工具类。

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /**
     * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(
            RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
        ).serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()
            )
        );

        return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
    }
}

3. 使用

通过注解的方式在Service的实现类中实现Redis缓存的应用。

在这主要用到三个常用的注解:@CachePut@Cacheable@CacheEvict

@CachePut 不会检查缓存中是否存在相同的key,执行方法的同时也会更新缓存。

@Cacheable 会检查缓存中是否存在相同的key,若存在,直接返回缓存结果而不会执行方法;若不存在,则在执行方法后,将返回结果进行缓存。

@CacheEvict 执行方法的同时也将对应的缓存进行清理。

import com.google.common.collect.Maps;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;

@Service
@Slf4j
public class UserServiceImpl implements UserService {
    /**
     * 模拟数据库
     */
    private static final Map<Long, User> DATABASES = Maps.newConcurrentMap();

    /**
     * 初始化数据
     */
    static {
        DATABASES.put(1L, new User(1L, "user1"));
        DATABASES.put(2L, new User(2L, "user2"));
        DATABASES.put(3L, new User(3L, "user3"));
    }

    /**
     * 保存或修改用户
     *
     * @param user 用户对象
     * @return 操作结果
     */
    @CachePut(value = "user", key = "#user.id")
    @Override
    public User saveOrUpdate(User user) {
        DATABASES.put(user.getId(), user);
        log.info("保存用户【user】= {}", user);
        return user;
    }

    /**
     * 获取用户
     *
     * @param id key值
     * @return 返回结果
     */
    @Cacheable(value = "user", key = "#id")
    @Override
    public User get(Long id) {
        // 我们假设从数据库读取
        log.info("查询用户【id】= {}", id);
        return DATABASES.get(id);
    }

    /**
     * 删除
     *
     * @param id key值
     */
    @CacheEvict(value = "user", key = "#id")
    @Override
    public void delete(Long id) {
        DATABASES.remove(id);
        log.info("删除用户【id】= {}", id);
    }
}

4. 测试

@Test
public void getTwice() {
    // 模拟查询id为1的用户
    User user1 = userService.get(1L);
    log.debug("【user1】= {}", user1);

    // 再次查询
    User user2 = userService.get(1L);
    log.debug("【user2】= {}", user2);
    // 查看日志,只打印一次 userService.get() 方法中的日志,证明缓存生效
}

@Test
public void getAfterSave() {
    userService.saveOrUpdate(new User(4L, "测试中文"));

    User user = userService.get(4L);
    log.debug("【user】= {}", user);
    // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效
}

@Test
public void deleteUser() {
    // 查询一次,使redis中存在缓存数据
    userService.get(1L);
    // 删除,查看redis是否存在缓存数据
    userService.delete(1L);

    // 再次查询,如果触发查询日志,说明已从缓存中删除。
    userService.get(1L);
}

文章作者: 叶遮沉阳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶遮沉阳 !
  目录