当前位置:   article > 正文

Spring Boot中开启Redis Cache并使用缓存注解_init redis cache

init redis cache

前序工作

该文章为如下两个工作的后续内容,在该文章的操作之前需要首先完成redis的安装和配置,以及Spring Boot和Redis的整合:

开启Spring Redis Cache

在Spring Boot中开启Redis Cache的过程比较简单,首先在application.properities配置文件中加入如下的redis cache配置项:

  1. # Spring Redis Cache
  2. # 设置缓存类型,这里使用Redis作为缓存服务器
  3. spring.cache.type=REDIS
  4. # 定义cache名称,用于在缓存注解中引用,多个名称可以使用逗号分隔
  5. spring.cache.cache-names=redisCache
  6. # 允许保存空值
  7. spring.cache.redis.cache-null-values=true
  8. # 自定义缓存前缀
  9. #spring.cache.redis.key-prefix=
  10. # 是否使用前缀
  11. spring.cache.redis.use-key-prefix=true
  12. # 设置缓存失效时间,0或者默认为永远不失效
  13. spring.cache.redis.time-to-live=0

上面的配置已经使用注释进行了说明,该配置其实是为缓存管理器CacheManager进行设置,这里将spring.cache.type设置为REDIS,即指定缓存管理器为RedisCacheManager。完成上述的配置,Spring Boot即会自动创建相应的缓存管理器来进行缓存的相关操作。

为了使用缓存管理器,还需要在Redis的配置类(或者整个项目的启动类)中加入驱动缓存的注解,这里继续Spring Boot集成Redis与使用RedisTemplate进行基本数据结构操作示例中配置类中添加该注解:

  1. @Configuration
  2. @EnableCaching // 开启Spring Redis Cache,使用注解驱动缓存机制
  3. public class SpringRedisConfiguration {
  4. @Autowired
  5. private RedisTemplate redisTemplate;
  6. /**
  7. * 利用Bean生命周期使用PostConstruct注解自定义后初始化方法
  8. */
  9. @PostConstruct
  10. public void init() {
  11. initRedisTemplate();
  12. }
  13. /**
  14. * 设置RedisTemplate的序列化器
  15. */
  16. private void initRedisTemplate() {
  17. RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
  18. // 将Key和其散列表数据类型的filed都修改为使用StringRedisSerializer进行序列化
  19. redisTemplate.setKeySerializer(stringSerializer);
  20. redisTemplate.setHashKeySerializer(stringSerializer);
  21. }
  22. }

这样即完成了cache的开启,下面构建cache的相关测试逻辑来使用缓存注解操作缓存数据。

注,这里同时在springboot的配置文件中设置了默认的数据库隔离级别为读写提交,以避免在使用数据库事务使产生脏读:

  1. # 设置默认的隔离级别为读写提交
  2. spring.datasource.tomcat.default-transaction-isolation=2

创建测试逻辑

下面创建简单的java对象,以及相应的操作逻辑,来使用缓存注解对Redis的cache进行管理。

简单POJO对象

  1. @Data
  2. @Alias(value = "user")
  3. public class User implements Serializable {
  4. // 开启Spring Redis Cache时,加入序列化
  5. private static final long serialVersionUID = -4947062488310146862L;
  6. private Long id;
  7. @NotNull(message = "用户名不能为空")
  8. private String userName;
  9. @NotNull(message = "备注不能为空")
  10. private String note;
  11. @NotNull(message = "性别不能为空")
  12. private SexEnum sex;
  13. }

MyBatis映射接口及配置文件

MyBatis映射接口如下:

  1. @Repository
  2. public interface UserMapper {
  3. User getUserById(Long id);
  4. int insertUser(User user);
  5. int updateUserById(User user);
  6. int deleteUser(Long id);
  7. @Select("select * from t_user where user_name = #{userName}")
  8. List<User> getUsersByName(@Param("userName") String userName);
  9. }

相应的映射文件如下(这里在操作MyBatis是同时使用了注解和映射文件,可以选择都是使用简单的注解或都使用映射文件进行实现) :

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="cn.zyt.springbootlearning.dao.UserMapper">
  4. <select id="getUserById" parameterType="long" resultType="user">
  5. select id, user_name as userName, sex, note from t_user where id = #{id}
  6. </select>
  7. <insert id="insertUser" parameterType="cn.zyt.springbootlearning.domain.User" useGeneratedKeys="true" keyProperty="id">
  8. insert into t_user (user_name, sex, note) values (#{userName}, #{sex}, #{note})
  9. </insert>
  10. <update id="updateUserById" parameterType="cn.zyt.springbootlearning.domain.User">
  11. update t_user set user_name=#{userName}, sex=#{sex}, note=#{note} where id=#{id}
  12. </update>
  13. <delete id="deleteUser" parameterType="long">
  14. delete from t_user where id=#{id}
  15. </delete>
  16. </mapper>

Service类

创建相关的service类和其实现类:

  1. public interface UserCacheService {
  2. User getUser(Long id);
  3. User insertUser(User user);
  4. User updateUserName(Long id, String userName);
  5. List<User> findUsers(String userName);
  6. int deleteUser(Long id);
  7. }

其实现类如下:

  1. @Service
  2. public class UserCacheServiceImpl implements UserCacheService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. /**
  6. * 使用Transactional注解开启事务
  7. * Cacheable注解表示先从缓存中通过定义的键值进行查询,如果查询不到则进行数据库查询并将查询结果保存到缓存中,其中:
  8. * value属性为spring application.properties配置文件中设置的缓存名称
  9. * key表示缓存的键值名称,其中id说明该方法需要一个名为id的参数
  10. */
  11. @Override
  12. @Transactional
  13. @Cacheable(value = "redisCache", key = "'redis_user_'+#id")
  14. public User getUser(Long id) {
  15. return userMapper.getUserById(id);
  16. }
  17. /**
  18. * CachePut注解表示将方法的返回结果存放到缓存中
  19. * value和key属性与上述意义一样,需要注意的是,key中的使用了result.id的方式
  20. * 这里的result表示该方法的返回值对象,即为user,id为取该对象的id属性值
  21. *
  22. * 这里在插入user时,传入的user参数是不存在id属性的,在mapper.xml文件中insertUser使用了如下的属性设置:
  23. * useGeneratedKeys="true" keyProperty="id"
  24. * 意味着,user的id属性会进行自增,并在use插入成功后会将指定的id属性进行回填,因此如下方法的返回值为带有id属性的完整user对象
  25. */
  26. @Override
  27. @Transactional
  28. @CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
  29. public User insertUser(User user) {
  30. userMapper.insertUser(user);
  31. System.out.println("After insert, User is: " + user);
  32. return user;
  33. }
  34. /**
  35. * 这里在CachePut注解中使用了condition配置项,它是一个Spring的EL,这个表达式要求返回Boolean类型的值,如果为true
  36. * 则使用缓存操作,否则不使用。
  37. */
  38. @Override
  39. @Transactional
  40. @CachePut(value = "redisCache", condition = "#result != null ", key = "'redis_user_'+#id")
  41. public User updateUserName(Long id, String userName) {
  42. User user = userMapper.getUserById(id);
  43. if (user == null) {
  44. return null;
  45. }
  46. user.setUserName(userName);
  47. userMapper.updateUserById(user);
  48. return user;
  49. }
  50. /**
  51. * 命中率低,所以不采用缓存机制
  52. */
  53. @Override
  54. @Transactional
  55. public List<User> findUsers(String userName) {
  56. return userMapper.getUsersByName(userName);
  57. }
  58. /**
  59. * CacheEvict注解通过定义的键移除相应的缓存,beforeInvocation属性表示是在方法执行之前还是之后移除缓存,默认为false,即为方法之后
  60. * 移除缓存
  61. */
  62. @Override
  63. @Transactional
  64. @CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
  65. public int deleteUser(Long id) {
  66. return userMapper.deleteUser(id);
  67. }
  68. }

缓存注解@Cacheable,@CachePut,@CacheEvict的使用如上面代码中的注释部分所示。 

测试Controller类

创建相应的Controller以供测试:

  1. @Controller
  2. @RequestMapping("/user/cache")
  3. public class UserCacheController {
  4. @Autowired
  5. private UserCacheService userCacheService;
  6. /**
  7. * 根据ID获取User
  8. */
  9. @RequestMapping("/getUser")
  10. @ResponseBody
  11. public CommonResult getUser(Long id) {
  12. User user = userCacheService.getUser(id);
  13. return new CommonResult(true, "获取成功", user);
  14. }
  15. /**
  16. * 插入一个新User
  17. */
  18. @RequestMapping("/insertUser")
  19. @ResponseBody
  20. public CommonResult insertUser(String userName, int sex, String note) {
  21. User user = new User(userName, sex, note);
  22. User resultUser = userCacheService.insertUser(user);
  23. return new CommonResult(true, "新增成功", resultUser);
  24. }
  25. /**
  26. * 根据Id查找用户并更新username
  27. */
  28. @RequestMapping("/updateUserName")
  29. @ResponseBody
  30. public CommonResult updateUserName(Long id, String userName) {
  31. User user = userCacheService.updateUserName(id, userName);
  32. boolean flag = user != null;
  33. String msg = flag ? "更新成功" : "更新失败";
  34. return new CommonResult(flag, msg, user);
  35. }
  36. /**
  37. * 根据Username查找UserList
  38. */
  39. @RequestMapping("/findUsers")
  40. @ResponseBody
  41. public CommonResult findUsers(String userName) {
  42. List<User> users = userCacheService.findUsers(userName);
  43. return new CommonResult(true, "查找成功", users);
  44. }
  45. /**
  46. * 删除用户
  47. */
  48. @RequestMapping("/deleteUser")
  49. @ResponseBody
  50. public CommonResult deleteUser(Long id) {
  51. int result = userCacheService.deleteUser(id);
  52. boolean flag = result == 1;
  53. String msg = flag ? "删除成功" : "删除失败";
  54. return new CommonResult(false, msg);
  55. }
  56. }

Cache测试结果

insertUser测试

在进行测试之前,先通过redis-cli客户端命令行对数据库中的数据进行清除:

  1. 127.0.0.1:6379> flushall
  2. OK
  3. 127.0.0.1:6379> keys *
  4. (empty list or set)

下面对上述构建的几个不同的方法进行cache的测试,首先请求insertUser插入一条记录:

http://localhost:8080/user/cache/insertUser?userName=yitian_cache&sex=1&note=none

返回结果如下:

数据库中已经存在相应的数据,查看redis中的keys如下,可以看到将插入的相应的数据缓存到了Redis中:

  1. 127.0.0.1:6379> keys *
  2. 1) "redisCache::redis_user_26"

getUser测试 

此时如果对该id的user进行数据查询时:

http://localhost:8080/user/cache/getUser?id=26

会之前从redis缓存中获得数据,而不会发送SQL,如下:

  1. 2020-02-13 11:31:57.475 DEBUG 57559 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
  2. 2020-02-13 11:31:57.475 DEBUG 57559 --- [nio-8080-exec-2] o.a.tomcat.util.net.SocketWrapperBase : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@14322c76:org.apache.tomcat.util.net.NioChannel@4e7228f6:java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:8080 remote=/0:0:0:0:0:0:0:1:53303]], Read from buffer: [0]
  3. 2020-02-13 11:31:57.475 DEBUG 57559 --- [nio-8080-exec-2] org.apache.tomcat.util.net.NioEndpoint : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@14322c76:org.apache.tomcat.util.net.NioChannel@4e7228f6:java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:8080 remote=/0:0:0:0:0:0:0:1:53303]], Read direct from socket: [0]
  4. 2020-02-13 11:31:57.475 DEBUG 57559 --- [nio-8080-exec-2] o.apache.coyote.http11.Http11Processor : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@14322c76:org.apache.tomcat.util.net.NioChannel@4e7228f6:java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:8080 remote=/0:0:0:0:0:0:0:1:53303]], Status in: [OPEN_READ], State out: [OPEN]

但如果测试从数据库中查询一个之前已经存在(但缓存中不存在的数据)时,例如id=1,可以从日志中看到sqlsession和transactional的构建和提交,此时会从数据库查询数据:

  1. 2020-02-13 11:37:25.944 DEBUG 57559 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/user/cache/getUser?id=1", parameters={masked}
  2. 2020-02-13 11:37:25.944 DEBUG 57559 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cn.zyt.springbootlearning.controller.UserCacheController#getUser(Long)
  3. 2020-02-13 11:37:25.950 DEBUG 57559 --- [nio-8080-exec-8] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [cn.zyt.springbootlearning.service.impl.UserCacheServiceImpl.getUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
  4. 2020-02-13 11:37:25.972 DEBUG 57559 --- [nio-8080-exec-8] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@519212670 wrapping com.mysql.jdbc.JDBC4Connection@1dd6a3e5] for JDBC transaction
  5. 2020-02-13 11:37:25.974 DEBUG 57559 --- [nio-8080-exec-8] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@519212670 wrapping com.mysql.jdbc.JDBC4Connection@1dd6a3e5] to manual commit
  6. 2020-02-13 11:37:26.001 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
  7. 2020-02-13 11:37:26.003 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d084971]
  8. 2020-02-13 11:37:26.007 DEBUG 57559 --- [nio-8080-exec-8] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@519212670 wrapping com.mysql.jdbc.JDBC4Connection@1dd6a3e5] will be managed by Spring
  9. 2020-02-13 11:37:26.008 DEBUG 57559 --- [nio-8080-exec-8] c.z.s.dao.UserMapper.getUserById : ==> Preparing: select id, user_name as userName, sex, note from t_user where id = ?
  10. 2020-02-13 11:37:26.021 DEBUG 57559 --- [nio-8080-exec-8] c.z.s.dao.UserMapper.getUserById : ==> Parameters: 1(Long)
  11. 2020-02-13 11:37:26.053 DEBUG 57559 --- [nio-8080-exec-8] c.z.s.dao.UserMapper.getUserById : <== Total: 1
  12. 2020-02-13 11:37:26.053 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d084971]
  13. 2020-02-13 11:37:26.053 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d084971]
  14. 2020-02-13 11:37:26.181 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d084971]
  15. 2020-02-13 11:37:26.181 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d084971]
  16. 2020-02-13 11:37:26.181 DEBUG 57559 --- [nio-8080-exec-8] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit

注,如上的日志输出为DEBUG级别,需要在application.properties配置文件中进行如下设置:

  1. # 日志配置
  2. logging.level.root=DEBUG
  3. logging.level.org.springframework=DEBUG
  4. logging.level.org.org.mybatis=DEBUG

以查看详细的日志输出。 

 当数据查询成功时,同样会将结果存放到redis缓存中:

  1. 127.0.0.1:6379> keys *
  2. 1) "redisCache::redis_user_1"
  3. 2) "redisCache::redis_user_26"

updateUserName测试 

同样的,如果使用updateUserName方法对user进行更新时,也会将redis中不存在的缓存数据加入到缓存中:

http://localhost:8080/user/cache/updateUserName?id=23&userName=yitian_new

成功更新完成后,redis中数据如下:

  1. 127.0.0.1:6379> keys *
  2. 1) "redisCache::redis_user_1"
  3. 2) "redisCache::redis_user_26"
  4. 3) "redisCache::redis_user_23"

但需要注意的是,在更新数据时缓存中的数据有可能是脏数据,所以在updateUserName方法中首先对数据库的数据进行了获取,然后在对该数据进行更新,从而避免之前从缓存中读取可以过时的数据。这一点需要注意。 

findUser测试

由于在使用findUser查询用户列表时,缓存的命中率会很低(因为查询的参数可能存在很大差异),所以这里没有设置缓存的使用。请求如下:

http://localhost:8080/user/cache/findUsers?userName=yitian

返回结果如下:

deleteUser测试

在对指定id的用户进行删除时,通过上述缓存注解的使用,会在方法调用完成后将缓存中的数据删除,这里使用上面添加的id=26的数据进行测试:

http://localhost:8080/user/cache/deleteUser?id=26

删除成功后redis中的结果如下:

  1. 127.0.0.1:6379> keys *
  2. 1) "redisCache::redis_user_1"
  3. 2) "redisCache::redis_user_23"

注意,在缓存注解中设置的value名会用于缓存的匹配,所以该名称需要在插入和删除时保持一致,否则在删除数据时不会匹配到正确的缓存数据,导致缓存删不掉。 

自定义缓存管理器

更新Redis缓存管理器配置

使用以上的缓存管理器的配置时,默认缓存的名称为{cacheName}::#{key}的形式,并且缓存永不失效。在application.properties文件中可以进行相应的配置:

  1. # 自定义缓存前缀
  2. spring.cache.redis.key-prefix=
  3. # 是否使用前缀
  4. spring.cache.redis.use-key-prefix=false
  5. # 设置缓存失效时间,0或者默认为永远不失效
  6. spring.cache.redis.time-to-live=600000

上面的设置即是将缓存前缀去掉, 只使用key作为缓存名,同时将缓存失效时间设置为600s,即10分钟。这样10分钟过后,redis的键就会超时,缓存会在数据操作时进行更新。

自定义缓存管理器

在对Spring Boot中缓存管理器进行设置时,除了如上使用配置文件的方式,还可以通过自定义缓存管理器来创建需要的缓存管理器并进行设置,当需要的自定义设置比较多时,推荐使用这种方式。

在上述的SpringRedisConfiguration.java配置类中进行如下定义:

  1. /**
  2. * 自定义RedisCacheManager
  3. */
  4. // @Bean(name = "redisCacheManager")
  5. public RedisCacheManager initRedisCacheManager() {
  6. // 获取Redis加锁的写入器
  7. RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
  8. // 启动Redis缓存的默认设置
  9. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
  10. // 设置JDK序列化器
  11. config = config.serializeValuesWith(
  12. RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
  13. // 自定义设置:禁用前缀
  14. config = config.disableKeyPrefix();
  15. // 设置失效时间
  16. config = config.entryTtl(Duration.ofMinutes(10));
  17. // 创建Redis缓存管理器
  18. RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
  19. return redisCacheManager;
  20. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/389692
推荐阅读
相关标签
  

闽ICP备14008679号