zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    file_get_contents抓取远程URL内容
    Xshell下VI打开文件中文乱码解决
    YII实现Memcache故障转移的配置办法
    Nginx实现多重IF判断的办法
    CentOS安装NodeJS v0.10.25 + Express
    一个小玩意 PHP实现微信红包金额拆分试玩
    Web Service测试利器 Postman
    CentOS安装Git
    PHP导出CSV UTF-8转GBK不乱码的解决办法
    configure: error: C++ compiler cannot create executables
  • 原文地址:https://www.cnblogs.com/yixianyixian/p/7427878.html
Copyright © 2011-2022 走看看