赞
踩
该文章为如下两个工作的后续内容,在该文章的操作之前需要首先完成redis的安装和配置,以及Spring Boot和Redis的整合:
在Spring Boot中开启Redis Cache的过程比较简单,首先在application.properities配置文件中加入如下的redis cache配置项:
- # Spring Redis Cache
- # 设置缓存类型,这里使用Redis作为缓存服务器
- spring.cache.type=REDIS
- # 定义cache名称,用于在缓存注解中引用,多个名称可以使用逗号分隔
- spring.cache.cache-names=redisCache
- # 允许保存空值
- spring.cache.redis.cache-null-values=true
- # 自定义缓存前缀
- #spring.cache.redis.key-prefix=
- # 是否使用前缀
- spring.cache.redis.use-key-prefix=true
- # 设置缓存失效时间,0或者默认为永远不失效
- spring.cache.redis.time-to-live=0
上面的配置已经使用注释进行了说明,该配置其实是为缓存管理器CacheManager进行设置,这里将spring.cache.type设置为REDIS,即指定缓存管理器为RedisCacheManager。完成上述的配置,Spring Boot即会自动创建相应的缓存管理器来进行缓存的相关操作。
为了使用缓存管理器,还需要在Redis的配置类(或者整个项目的启动类)中加入驱动缓存的注解,这里继续Spring Boot集成Redis与使用RedisTemplate进行基本数据结构操作示例中配置类中添加该注解:
- @Configuration
- @EnableCaching // 开启Spring Redis Cache,使用注解驱动缓存机制
- public class SpringRedisConfiguration {
- @Autowired
- private RedisTemplate redisTemplate;
-
- /**
- * 利用Bean生命周期使用PostConstruct注解自定义后初始化方法
- */
- @PostConstruct
- public void init() {
- initRedisTemplate();
- }
-
- /**
- * 设置RedisTemplate的序列化器
- */
- private void initRedisTemplate() {
- RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
- // 将Key和其散列表数据类型的filed都修改为使用StringRedisSerializer进行序列化
- redisTemplate.setKeySerializer(stringSerializer);
- redisTemplate.setHashKeySerializer(stringSerializer);
- }
- }
这样即完成了cache的开启,下面构建cache的相关测试逻辑来使用缓存注解操作缓存数据。
注,这里同时在springboot的配置文件中设置了默认的数据库隔离级别为读写提交,以避免在使用数据库事务使产生脏读:
- # 设置默认的隔离级别为读写提交
- spring.datasource.tomcat.default-transaction-isolation=2
下面创建简单的java对象,以及相应的操作逻辑,来使用缓存注解对Redis的cache进行管理。
- @Data
- @Alias(value = "user")
- public class User implements Serializable {
- // 开启Spring Redis Cache时,加入序列化
- private static final long serialVersionUID = -4947062488310146862L;
-
- private Long id;
- @NotNull(message = "用户名不能为空")
- private String userName;
- @NotNull(message = "备注不能为空")
- private String note;
- @NotNull(message = "性别不能为空")
- private SexEnum sex;
- }
MyBatis映射接口如下:
- @Repository
- public interface UserMapper {
- User getUserById(Long id);
-
- int insertUser(User user);
-
- int updateUserById(User user);
-
- int deleteUser(Long id);
-
- @Select("select * from t_user where user_name = #{userName}")
- List<User> getUsersByName(@Param("userName") String userName);
- }
相应的映射文件如下(这里在操作MyBatis是同时使用了注解和映射文件,可以选择都是使用简单的注解或都使用映射文件进行实现) :
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="cn.zyt.springbootlearning.dao.UserMapper">
-
- <select id="getUserById" parameterType="long" resultType="user">
- select id, user_name as userName, sex, note from t_user where id = #{id}
- </select>
-
- <insert id="insertUser" parameterType="cn.zyt.springbootlearning.domain.User" useGeneratedKeys="true" keyProperty="id">
- insert into t_user (user_name, sex, note) values (#{userName}, #{sex}, #{note})
- </insert>
-
- <update id="updateUserById" parameterType="cn.zyt.springbootlearning.domain.User">
- update t_user set user_name=#{userName}, sex=#{sex}, note=#{note} where id=#{id}
- </update>
-
- <delete id="deleteUser" parameterType="long">
- delete from t_user where id=#{id}
- </delete>
- </mapper>
创建相关的service类和其实现类:
- public interface UserCacheService {
- User getUser(Long id);
- User insertUser(User user);
- User updateUserName(Long id, String userName);
- List<User> findUsers(String userName);
- int deleteUser(Long id);
- }
其实现类如下:
- @Service
- public class UserCacheServiceImpl implements UserCacheService {
-
- @Autowired
- private UserMapper userMapper;
-
- /**
- * 使用Transactional注解开启事务
- * Cacheable注解表示先从缓存中通过定义的键值进行查询,如果查询不到则进行数据库查询并将查询结果保存到缓存中,其中:
- * value属性为spring application.properties配置文件中设置的缓存名称
- * key表示缓存的键值名称,其中id说明该方法需要一个名为id的参数
- */
- @Override
- @Transactional
- @Cacheable(value = "redisCache", key = "'redis_user_'+#id")
- public User getUser(Long id) {
- return userMapper.getUserById(id);
- }
-
- /**
- * CachePut注解表示将方法的返回结果存放到缓存中
- * value和key属性与上述意义一样,需要注意的是,key中的使用了result.id的方式
- * 这里的result表示该方法的返回值对象,即为user,id为取该对象的id属性值
- *
- * 这里在插入user时,传入的user参数是不存在id属性的,在mapper.xml文件中insertUser使用了如下的属性设置:
- * useGeneratedKeys="true" keyProperty="id"
- * 意味着,user的id属性会进行自增,并在use插入成功后会将指定的id属性进行回填,因此如下方法的返回值为带有id属性的完整user对象
- */
- @Override
- @Transactional
- @CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
- public User insertUser(User user) {
- userMapper.insertUser(user);
- System.out.println("After insert, User is: " + user);
- return user;
- }
-
- /**
- * 这里在CachePut注解中使用了condition配置项,它是一个Spring的EL,这个表达式要求返回Boolean类型的值,如果为true
- * 则使用缓存操作,否则不使用。
- */
- @Override
- @Transactional
- @CachePut(value = "redisCache", condition = "#result != null ", key = "'redis_user_'+#id")
- public User updateUserName(Long id, String userName) {
- User user = userMapper.getUserById(id);
- if (user == null) {
- return null;
- }
- user.setUserName(userName);
- userMapper.updateUserById(user);
- return user;
- }
-
- /**
- * 命中率低,所以不采用缓存机制
- */
- @Override
- @Transactional
- public List<User> findUsers(String userName) {
- return userMapper.getUsersByName(userName);
- }
-
- /**
- * CacheEvict注解通过定义的键移除相应的缓存,beforeInvocation属性表示是在方法执行之前还是之后移除缓存,默认为false,即为方法之后
- * 移除缓存
- */
- @Override
- @Transactional
- @CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
- public int deleteUser(Long id) {
- return userMapper.deleteUser(id);
- }
- }
缓存注解@Cacheable,@CachePut,@CacheEvict的使用如上面代码中的注释部分所示。
创建相应的Controller以供测试:
- @Controller
- @RequestMapping("/user/cache")
- public class UserCacheController {
-
- @Autowired
- private UserCacheService userCacheService;
-
- /**
- * 根据ID获取User
- */
- @RequestMapping("/getUser")
- @ResponseBody
- public CommonResult getUser(Long id) {
- User user = userCacheService.getUser(id);
- return new CommonResult(true, "获取成功", user);
- }
-
- /**
- * 插入一个新User
- */
- @RequestMapping("/insertUser")
- @ResponseBody
- public CommonResult insertUser(String userName, int sex, String note) {
- User user = new User(userName, sex, note);
- User resultUser = userCacheService.insertUser(user);
- return new CommonResult(true, "新增成功", resultUser);
- }
-
- /**
- * 根据Id查找用户并更新username
- */
- @RequestMapping("/updateUserName")
- @ResponseBody
- public CommonResult updateUserName(Long id, String userName) {
- User user = userCacheService.updateUserName(id, userName);
- boolean flag = user != null;
- String msg = flag ? "更新成功" : "更新失败";
- return new CommonResult(flag, msg, user);
- }
-
- /**
- * 根据Username查找UserList
- */
- @RequestMapping("/findUsers")
- @ResponseBody
- public CommonResult findUsers(String userName) {
- List<User> users = userCacheService.findUsers(userName);
- return new CommonResult(true, "查找成功", users);
- }
-
- /**
- * 删除用户
- */
- @RequestMapping("/deleteUser")
- @ResponseBody
- public CommonResult deleteUser(Long id) {
- int result = userCacheService.deleteUser(id);
- boolean flag = result == 1;
- String msg = flag ? "删除成功" : "删除失败";
- return new CommonResult(false, msg);
-
- }
- }
在进行测试之前,先通过redis-cli客户端命令行对数据库中的数据进行清除:
- 127.0.0.1:6379> flushall
- OK
- 127.0.0.1:6379> keys *
- (empty list or set)
下面对上述构建的几个不同的方法进行cache的测试,首先请求insertUser插入一条记录:
http://localhost:8080/user/cache/insertUser?userName=yitian_cache&sex=1¬e=none
返回结果如下:
数据库中已经存在相应的数据,查看redis中的keys如下,可以看到将插入的相应的数据缓存到了Redis中:
- 127.0.0.1:6379> keys *
- 1) "redisCache::redis_user_26"
此时如果对该id的user进行数据查询时:
http://localhost:8080/user/cache/getUser?id=26
会之前从redis缓存中获得数据,而不会发送SQL,如下:
- 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
- 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]
- 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]
- 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的构建和提交,此时会从数据库查询数据:
- 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}
- 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)
- 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
- 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
- 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
- 2020-02-13 11:37:26.001 DEBUG 57559 --- [nio-8080-exec-8] org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
- 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]
- 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
- 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 = ?
- 2020-02-13 11:37:26.021 DEBUG 57559 --- [nio-8080-exec-8] c.z.s.dao.UserMapper.getUserById : ==> Parameters: 1(Long)
- 2020-02-13 11:37:26.053 DEBUG 57559 --- [nio-8080-exec-8] c.z.s.dao.UserMapper.getUserById : <== Total: 1
- 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]
- 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]
- 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]
- 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]
- 2020-02-13 11:37:26.181 DEBUG 57559 --- [nio-8080-exec-8] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
注,如上的日志输出为DEBUG级别,需要在application.properties配置文件中进行如下设置:
# 日志配置 logging.level.root=DEBUG logging.level.org.springframework=DEBUG logging.level.org.org.mybatis=DEBUG以查看详细的日志输出。
当数据查询成功时,同样会将结果存放到redis缓存中:
- 127.0.0.1:6379> keys *
- 1) "redisCache::redis_user_1"
- 2) "redisCache::redis_user_26"
同样的,如果使用updateUserName方法对user进行更新时,也会将redis中不存在的缓存数据加入到缓存中:
http://localhost:8080/user/cache/updateUserName?id=23&userName=yitian_new
成功更新完成后,redis中数据如下:
- 127.0.0.1:6379> keys *
- 1) "redisCache::redis_user_1"
- 2) "redisCache::redis_user_26"
- 3) "redisCache::redis_user_23"
但需要注意的是,在更新数据时缓存中的数据有可能是脏数据,所以在updateUserName方法中首先对数据库的数据进行了获取,然后在对该数据进行更新,从而避免之前从缓存中读取可以过时的数据。这一点需要注意。
由于在使用findUser查询用户列表时,缓存的命中率会很低(因为查询的参数可能存在很大差异),所以这里没有设置缓存的使用。请求如下:
http://localhost:8080/user/cache/findUsers?userName=yitian
返回结果如下:
在对指定id的用户进行删除时,通过上述缓存注解的使用,会在方法调用完成后将缓存中的数据删除,这里使用上面添加的id=26的数据进行测试:
http://localhost:8080/user/cache/deleteUser?id=26
删除成功后redis中的结果如下:
- 127.0.0.1:6379> keys *
- 1) "redisCache::redis_user_1"
- 2) "redisCache::redis_user_23"
注意,在缓存注解中设置的value名会用于缓存的匹配,所以该名称需要在插入和删除时保持一致,否则在删除数据时不会匹配到正确的缓存数据,导致缓存删不掉。
使用以上的缓存管理器的配置时,默认缓存的名称为{cacheName}::#{key}的形式,并且缓存永不失效。在application.properties文件中可以进行相应的配置:
- # 自定义缓存前缀
- spring.cache.redis.key-prefix=
- # 是否使用前缀
- spring.cache.redis.use-key-prefix=false
- # 设置缓存失效时间,0或者默认为永远不失效
- spring.cache.redis.time-to-live=600000
上面的设置即是将缓存前缀去掉, 只使用key作为缓存名,同时将缓存失效时间设置为600s,即10分钟。这样10分钟过后,redis的键就会超时,缓存会在数据操作时进行更新。
在对Spring Boot中缓存管理器进行设置时,除了如上使用配置文件的方式,还可以通过自定义缓存管理器来创建需要的缓存管理器并进行设置,当需要的自定义设置比较多时,推荐使用这种方式。
在上述的SpringRedisConfiguration.java配置类中进行如下定义:
- /**
- * 自定义RedisCacheManager
- */
- // @Bean(name = "redisCacheManager")
- public RedisCacheManager initRedisCacheManager() {
- // 获取Redis加锁的写入器
- RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
- // 启动Redis缓存的默认设置
- RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
- // 设置JDK序列化器
- config = config.serializeValuesWith(
- RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
-
- // 自定义设置:禁用前缀
- config = config.disableKeyPrefix();
- // 设置失效时间
- config = config.entryTtl(Duration.ofMinutes(10));
- // 创建Redis缓存管理器
- RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
- return redisCacheManager;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。