Spring Boot中使用缓存

随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。

原始的使用缓存的方式如下:这样的缓存使用方式将数据读取后,主动对缓存进行更新操作,这样的方式使用方便,但是代码的耦合性高,代码侵入性强。

 1  /**
 2      * 使用缓存以id为字样,如果id所对应的缓存信息已经存在,则不会再读db
 3      * @param id
 4      * @return
 5      */
 6     public UserInfo getUserInfoById(int id){
 7         UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(id+"");
 8         if(userInfo != null){
 9             return userInfo;
10         }
11         System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");
12         UserInfo userInfo1 = userInfoDao.findById(id);
13         ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue();
14         valueOperations.set(id+"", userInfo1);
15         return userInfo1;
16     }
17 
18     @Transactional
19     public int saveUserInfo(UserInfo userInfo){
20         //更新缓存
21         userInfoDao.saveUserInfo(userInfo);
22         int id = userInfo.getId();
23         //userInfo 里面的id值已经发生了变化
24         System.out.println(userInfo.getId());
25         ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue();
26         valueOperations.set(id+"", userInfo);
27         redisTemplate.opsForValue().set(id+"", userInfo);
28         return id;
29     }
View Code

Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。

在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示,我们是如何给一个既有应用增加缓存功能的。

引入缓存

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

在application.preoperties中定义redis的配置

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
View Code

 

自定义缓存,本文使用redis作为缓存,自定义缓存配置,继承CachingConfigurerSupport

  1 /**
  2  * 自定义缓存配置文件,继承 CachingConfigurerSupport
  3  * Created by huanl on 2017/8/22.
  4  */
  5 @Configuration
  6 @EnableCaching
  7 public class RedisConfig extends CachingConfigurerSupport{
  8     public RedisConfig() {
  9         super();
 10     }
 11 
 12     /**
 13      * 指定使用哪一种缓存
 14      * @param redisTemplate
 15      * @return
 16      */
 17     @Bean
 18     public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
 19         RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
 20         return rcm;
 21     }
 22 
 23     /**
 24      * 指定默认的key生成方式
 25      * @return
 26      */
 27     @Override
 28     public KeyGenerator keyGenerator() {
 29        KeyGenerator keyGenerator = new KeyGenerator() {
 30            @Override
 31            public Object generate(Object o, Method method, Object... objects) {
 32                StringBuilder sb = new StringBuilder();
 33                sb.append(o.getClass().getName());
 34                sb.append(method.getName());
 35                for (Object obj : objects) {
 36                    sb.append(obj.toString());
 37                }
 38                return sb.toString();
 39            }
 40        };
 41        return keyGenerator;
 42     }
 43 
 44     @Override
 45     public CacheResolver cacheResolver() {
 46         return super.cacheResolver();
 47     }
 48 
 49     @Override
 50     public CacheErrorHandler errorHandler() {
 51         return super.errorHandler();
 52     }
 53 
 54     /**
 55      * redis 序列化策略 ,通常情况下key值采用String序列化策略
 56      * StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。StringRedisSerializer
 57      * RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。JdkSerializationRedisSerializer
 58      * @param factory
 59      * @return
 60      */
 61     @Bean
 62     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory){
 63         RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
 64         redisTemplate.setConnectionFactory(factory);
 65 
 66 //        // 使用Jackson2JsonRedisSerialize 替换默认序列化
 67 //        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
 68 //        ObjectMapper om = new ObjectMapper();
 69 //        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 70 //        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 71 //        jackson2JsonRedisSerializer.setObjectMapper(om);
 72 //
 73 //
 74 //        //设置value的序列化方式
 75 //        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
 76 //        //设置key的序列化方式
 77 //        redisTemplate.setKeySerializer(new StringRedisSerializer());
 78 //        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
 79 //        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
 80 
 81         //使用fastJson作为默认的序列化方式
 82         GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
 83         redisTemplate.setDefaultSerializer(genericFastJsonRedisSerializer);
 84         redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
 85         redisTemplate.setKeySerializer(new StringRedisSerializer());
 86         redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
 87         redisTemplate.setHashKeySerializer(new StringRedisSerializer());
 88         redisTemplate.afterPropertiesSet();
 89 
 90         return redisTemplate;
 91 
 92     }
 93 
 94     /**
 95      * 转换返回的object为json
 96      * @return
 97      */
 98     @Bean
 99     public HttpMessageConverters fastJsonHttpMessageConverters(){
100         // 1、需要先定义一个converter 转换器
101         FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
102         // 2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据
103         FastJsonConfig fastJsonConfig = new FastJsonConfig();
104         fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
105         // 3、在convert 中添加配置信息
106         fastConverter.setFastJsonConfig(fastJsonConfig);
107         // 4、将convert 添加到converters当中
108         HttpMessageConverter<?> converter = fastConverter;
109         return new HttpMessageConverters(converter);
110     }
111 
112 
113 }
View Code

在Spring Boot主类中增加@EnableCaching注解开启缓存功能

 1 @SpringBootApplication
 2 @Import(RedisConfig.class)
 3 @MapperScan("com.redistest.dao")
 4 @EnableCaching
 5 public class RedisApplication {
 6 
 7     public static void main(String[] args) {
 8         SpringApplication.run(RedisApplication.class, args);
 9     }
10 }
View Code

在Service类中使用缓存

@Service
public class UserInfoService {

    @Autowired
    private UserInfoDao userInfoDao;

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 优先从缓存中获取数据,如果缓存中不存在,则从db中读取,读取后将结果按照key存入缓存
     * @param id
     * @return
     */
    @Cacheable(value = "user", key="#id + 'findById'")
    public UserInfo getUserInfoByIDNew(int id){
        return userInfoDao.findById(id);
    }

    /**
     * 使用缓存以id为字样,如果id所对应的缓存信息已经存在,则不会再读db
     * @param id
     * @return
     */
    public UserInfo getUserInfoById(int id){
        UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(id+"");
        if(userInfo != null){
            return userInfo;
        }
        System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");
        UserInfo userInfo1 = userInfoDao.findById(id);
        ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue();
        valueOperations.set(id+"", userInfo1);
        return userInfo1;
    }

    @Transactional
    public int saveUserInfo(UserInfo userInfo){
        //更新缓存
        userInfoDao.saveUserInfo(userInfo);
        int id = userInfo.getId();
        //userInfo 里面的id值已经发生了变化
        System.out.println(userInfo.getId());
        ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue();
        valueOperations.set(id+"", userInfo);
        redisTemplate.opsForValue().set(id+"", userInfo);
        return id;
    }

    /**
     *
     * @param userInfo
     * @return
     */
    @Transactional
    //更新后删除指定值的缓存,获取值得时候默认从db中获取
    //@CacheEvict(value = "user", key="#userInfo.id+'findById'")
    //配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
    @CachePut(value = "user", key = "#userInfo.id+'findById'")
    public UserInfo updateUserInfo(UserInfo userInfo){
        userInfoDao.updateUserInfo(userInfo);
        int id = userInfo.getId();
        redisTemplate.opsForValue().set(id+"", userInfo);
        return userInfo;
    }
}
View Code

使用的MybatisDao

@Mapper
@Component
public interface UserInfoDao {
    @Select(value = "select * from t_t_user where id=#{id}")
    public UserInfo findById(int id);

    public int saveUserInfo(UserInfo userInfo);

    public int updateUserInfo(UserInfo userInfo);
}
View Code

mapper文件,在application.properties文件中定义mapper文件的位置

mybatis.mapper-locations=mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        <!--命名空间:分类管理sql隔离,方便管理-->
<mapper namespace="com.redistest.dao.UserInfoDao">

    <!--插入-->
    <insert id="saveUserInfo" useGeneratedKeys="true" keyProperty="id" keyColumn="id" parameterType="com.redistest.domain.UserInfo">
      INSERT INTO t_t_user (name, password, salt, role) VALUES (#{name}, #{password}, #{salt}, #{role})
    </insert>

    <!--更新-->
    <update id="updateUserInfo" parameterType="com.redistest.domain.UserInfo">
        UPDATE t_t_user set name=#{name}, password=#{password}, salt=#{salt}, role=#{role} where id=#{id}
    </update>
</mapper>
View Code

Cache注解详解

回过头来我们再来看,这里使用到的两个注解分别作了什么事情。

  • @CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = "users"):配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。

  • @Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:

    • valuecacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
    • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
    • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。
    • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
    • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
    • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
    • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

除了这里用到的两个注解之外,还有下面几个核心注解:

  • @CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
  • @CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:
    • allEntries:非必需,默认为false。当为true时,会移除所有数据
    • beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。

缓存配置

完成了上面的缓存实验之后,可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢?

在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:

  • Generic
  • JCache (JSR-107)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Redis
  • Guava
  • Simple

除了按顺序侦测外,我们也可以通过配置属性spring.cache.type来强制指定。我们可以通过debug调试查看cacheManager对象的实例来判断当前使用了什么缓存。

本文中不对所有的缓存做详细介绍,下面以常用的EhCache为例,看看如何配置来使用EhCache进行缓存管理。

在Spring Boot中开启EhCache非常简单,只需要在工程中加入ehcache.xml配置文件并在pom.xml中增加ehcache依赖,框架只要发现该文件,就会创建EhCache的缓存管理器。

  • src/main/resources目录下创建:ehcache.xml
1
2
3
4
5
6
7
8
9
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
 
<cache name="users"
maxEntriesLocalHeap="200"
timeToLiveSeconds="600">
</cache>
 
</ehcache>
  • pom.xml中加入
1
2
3
4
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

完成上面的配置之后,再通过debug模式运行单元测试,观察此时CacheManager已经是EhCacheManager实例,说明EhCache开启成功了。

对于EhCache的配置文件也可以通过application.properties文件中使用spring.cache.ehcache.config属性来指定,比如:

1
spring.cache.ehcache.config=classpath:config/another-config.xml
posted @ 2017-08-25 14:26  一弦一仙  阅读(19560)  评论(0编辑  收藏  举报