coding……
但行好事 莫问前程

Spring Boot + Redis实现缓存

缓存作为开发中提高服务性能相对有效的一种方式,在实际开发中得到广泛使用。在Spring 3.1之前,如果想使用缓存,相对是比较麻烦的,往往在业务代码中要掺杂缓存的逻辑,比如判断缓存是否存在,存在则取缓存,不存在在从DB中读取,然后再讲数据存入缓存中,使用起来相当不方便。Spring 3.1引入了基于注释的缓存技术,它本质上不是一个具体的缓存实现方案(例如EHCache、Redis、MemoryCache),而是一个对缓存使用的抽象,通过在代码中添加少量注解,即能够达到缓存方法的返回对象的效果。也就是说底层具体缓存实现对于开发人员来讲是透明的,实现缓存和具体业务代码的解耦。目前Spring注解支持的缓存有java.util.concurrent.ConcurrentMap,Ehcache 2.x,Redis等。本文介绍如何通过Spring注解结合底层Redis实现缓存,首先要引入如下两个包:

spring-boot-starter-data-redis
spring-boot-starter-cache
redis.clients.jedis

1. 项目结构

|   pom.xml
|   springboot-13-redis-cache.iml
+---src
|   +---main
|   |   +---java
|   |   |   \---com
|   |   |       \---zhuoli
|   |   |           \---service
|   |   |               \---springboot
|   |   |                   \---redis
|   |   |                       \---cache
|   |   |                           |   SpringBootRedisCacheApplicationContext.java
|   |   |                           |
|   |   |                           +---controller
|   |   |                           |       UserController.java
|   |   |                           |
|   |   |                           +---repository
|   |   |                           |   +---conf
|   |   |                           |   |       DataSourceConfig.java
|   |   |                           |   |       RedisCacheConfig.java
|   |   |                           |   |
|   |   |                           |   +---mapper
|   |   |                           |   |       UserMapper.java
|   |   |                           |   |
|   |   |                           |   +---model
|   |   |                           |   |       User.java
|   |   |                           |   |       UserExample.java
|   |   |                           |   |
|   |   |                           |   \---service
|   |   |                           |       |   UserRepository.java
|   |   |                           |       |
|   |   |                           |       \---impl
|   |   |                           |               UserRepositoryImpl.java
|   |   |                           |
|   |   |                           \---service
|   |   |                               |   UserControllerService.java
|   |   |                               |
|   |   |                               \---impl
|   |   |                                       UserControllerServiceImpl.java
|   |   |
|   |   \---resources
|   |       |   application.properties
|   |       |
|   |       +---autogen
|   |       |       generatorConfig_zhuoli.xml
|   |       |
|   |       \---base
|   |           \---com
|   |               \---zhuoli
|   |                   \---service
|   |                       \---springboot
|   |                           \---redis
|   |                               \---cache
|   |                                   \---repository
|   |                                       \---mapper
|   |                                               UserMapper.xml
|   |
|   \---test
|       \---java

2. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhuoli.service</groupId>
    <artifactId>springboot-13-redis-cache</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Spring Boot 启动父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.5</version>
                <!--如果不配置configuration节点,配置文件名字必须为generatorConfig.xml-->
                <configuration>
                    <!--可以自定义generatorConfig文件名-->
                    <configurationFile>src/main/resources/autogen/generatorConfig_zhuoli.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- Exclude Spring Boot's Default Logging -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

3. RedisCache配置

@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }

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

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的key会使用这个
                this.getRedisCacheConfigurationMap() // 指定key策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("user", this.getRedisCacheConfigurationWithTtl(10));
        redisCacheConfigurationMap.put("other", this.getRedisCacheConfigurationWithTtl(18000));

        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }
}

这里分别讲一下RedisCacheConfig中的几个Bean配置

3.1 RedisConnectionFactory定义

RedisConnectionFactory主要用来定义Redis数据源连接,最开始我的想法是,通过调用JedisConnectionFactory的setHostName等方法去指定数据源连接的,如下:

@Bean
JedisConnectionFactory jedisConnectionFactory() {
    JedisConnectionFactory jedisConFactory
            = new JedisConnectionFactory();

    jedisConFactory.setHostName("localhost");
    jedisConFactory.setPort(6379);
    return jedisConFactory;
}

但是查看官方文档发现,Spring Boot 2.0后setHostName、setPort方法都已经过期了,Spring Boot 2.0后使用RedisStandaloneConfiguration替代查看文档后,RedisConnectionFactory Bean定义如下:

@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
    return new JedisConnectionFactory(redisStandaloneConfiguration);
}

3.2 定制RedisTemplate

自动配置的RedisTemplate并不能满足大部分项目的需求,比如我们基本都需要设置特定的Serializer(RedisTemplate默认会使用JdkSerializationRedisSerializer)。

Redis底层中存储的数据只是字节,虽然Redis本身支持各种类型(List, Hash等),但在大多数情况下,这些指的是数据的存储方式,而不是它所代表的内容(内容都是byte),用户自己来决定数据如何被转换成String或任何其他对象。用户自定义类型和原始数据类型之间的互相转换通过RedisSerializer接口(org.springframework.data.redis.serializer)来处理,顾名思义,它负责处理序列化/反序列化过程。包中多个实现可以开箱即用,比如:StringRedisSerializer和JdkSerializationRedisSerialize、用来处理JSON格式的数据的Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer。

上述RedisTemplate ValueSerializer序列化使用GenericJackson2JsonRedisSerializer而不是Jackson2JsonRedisSerializer,因为Jackson2JsonRedisSerializer需要为每一个需要序列化进Redis的类指定一个Jackson2JsonRedisSerializer,因为其构造函数中需要指定一个类型来做反序列化:

redis.setValueSerializer(new Jackson2JsonRedisSerializer<Product>(Product.class));

如果应用中有大量对象需要缓存,这显然是不合适的。而GenericJackson2JsonRedisSerializer直接把类型信息序列化到了JSON格式中,让一个实例可以操作多个对象的反序列化。

3.3 定制RedisCacheManager

有时候Spring Boot自动给我们配置的RedisCacheManager不能满足我们应用的需求,比如我们要对缓存的key进行设置声明周期,这时候可以通过自定义CacheManager的方式实现。由于Spring Boot2.0之后,RedisCacheManager类发生了很多改变,原来的一堆方法比如setExpires、setUsePrefix、setDefaultExpiration都已经不存在了,通过查看源码及文档,最终找到一种Spring Boot 2.*版本复合需求的RedisCache设置缓存声明周期的方法。其实RedisCacheManager还有很多其他的构造函数,可以按照具体需求选择。上述配置实现cache名称为user的缓存生命周期为10S,名称为other的缓存生命周期为18000S,除此之外所有cache的声明周期默认为600S。

4. 缓存注解介绍

Spring针对缓存提供的注解主要包含@Cacheable、@CachePut、@CacheEvict 三个,具体含义如下: 4.1 示例说明

@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)”) 缓存后的redis key为 user::username::password(其中username和password为EL表达式)

@CacheEvict(value=”user”,allEntries=true,beforeInvocation=true) 加上beforeInvocation=true后,不管内部是否报错,缓存都将被清除

5. 缓存使用

我在repository查询方法上添加缓存,实际开发中也可以把缓存放在service层。repository层缓存使用如下:

@Cacheable(value = "user", key = "#id")
@Override
public User getUserById(Long id) {
    log.info("数据库取数据");
    return userMapper.selectByPrimaryKey(id);
}

@Cacheable注解表示当第一次调用getUserById方法时,会进行数据库查询,并将查询到的结果User缓存到Redis,redis的key为user::id,@Cacheable注解中,key属性支持EL表达式,#id表示具体的参数id,比如方法请求id为6,则最终Redis缓存的key为user::6

@CacheEvict(value = "user", key = "#id")
@Override
public int delUserById(Long id) {
    UserExample example = new UserExample();
    example.createCriteria().andIdEqualTo(id);
    return userMapper.deleteByExample(example);
    /*等价于
    return userMapper.deleteByPrimaryKey(id);
    */
}

@CachePut(value = "user", key = "#user.id")
@Override
public User updateUser(User user) {
    userMapper.updateByPrimaryKey(user);
    return userMapper.selectByPrimaryKey(user.getId());
}

6. 开启缓存功能

@EnableCaching注解,打开缓存功能,如下:

@SpringBootApplication
@EnableCaching
@Import(value = {DataSourceConfig.class, RedisCacheConfig.class})
public class SpringBootRedisCacheApplicationContext {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootRedisCacheApplicationContext.class, args);
    }
}

篇幅原因,controller层、service层的代码这里就不展示了,有兴趣的同学可以到文章底部链接看一下示例代码。

7. 测试

之前在RedisCacheConfig中,我将名称为user的缓存声明周期设置为10S,是为了测试声明周期设置是否生效,根据测试结果显示,声明周期是生效的。正式进行测试之前,注意将user缓存声明周期设置为一个较大值。

7.1 @Cacheable

7.2 @CachePut

7.3 @CacheEvict

默认情况下,RedisCache 不会缓存任何null values,因为Redis会丢弃没有value的keys。使用CacheEvict清楚缓存后,会使用org.springframework.cache.support.NullValue作为占位符存储。

示例代码:码云 – 卓立 – Spring Boot + Redis实现缓存

参考链接:

  1. Spring Boot In Practice (1):Redis缓存实战
  2. 使用Spring Cache集成Redis
  3. 如何使用RedisTemplate访问Redis数据结构

赞(0) 打赏
Zhuoli's Blog » Spring Boot + Redis实现缓存
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏