1. 简介
当我们对redis的基本知识有一定的了解后,我们再通过实战的角度学习一下在SpringBoot环境下,如何优雅的使用redis。
我们通过使用SpringBoot内置的Redis注解(文章最后有解释)来操作User相关的信息。
再通过Redis工具类的方式操作Role相关信息来全面的学习Redis的使用。
1下载安装文件,选择稳定版本 2解压后找到bin目录下的release下的redis-2.8.17 3点击安装exe文件,进行安装。选择好路径,一直到安装结束即可。 4点击Service查看Redis服务是否正确的安装。Windows--》Service.msc。默认的端口。
嫌篇幅太长的 可以直接跳到2.6查看具体逻辑即可。
2. 开撸
2.1 项目结构
结构说明:
├── src│ └── main│ ├── java│ │ └── com│ │ └── ldx│ │ └── redis│ │ ├── RedisApplication.javaredis 配置类│ │ ├── constant│ │ │ └── CacheConstant.java角色管理控制器│ │ │ └── UserController.java角色entity│ │ │ └── SysUser.java角色持久层│ │ │ └── SysUserMapper.java角色接口层│ │ │ ├── SysUserService.java角色接口实现层│ │ │ └── SysUserServiceImpl.javaredis 工具类│ └── resources│ └── application.yaml依赖管理
2.2 导入依赖
<?xml version=&34; encoding=&34;?><project xmlns=&34;; xsi:schemaLocation=& redis 配置redis: 端口,默认为6379port: 6379 连接超时时间timeout: 10slettuce:pool: 连接池中的最大空闲连接max-idle: 8设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置mapper-locations: classpath*:mapper/*.xml 控制台sql打印log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 日志配置logging:level:com.ldx.redis.service.impl: debugorg.springframework: warn
2.3.2 启动类
@EnableCaching :激活缓存支持
@MapperScan : 扫描mapper接口层
import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;/** * 启动类 * @author ludangxin * @date 2021/8/11 */@EnableCaching@MapperScan(basePackages = &34;)@SpringBootApplicationpublic class RedisApplication {public static void main(String[] args) {SpringApplication.run(RedisApplication.class,args);}}
如果单纯地要解决这个问题的话,可以在设置value的时候使用一个随机数,释放锁的时候,先判断这个随机数是否一致,如果一致再删除锁,否则就退出。但是判断value和删除key也不是一个原子操作,这时候就需要使用lua脚本了。上面。
2.4 redis配置
2.4.1 RedisConfig
我们除了在application.yaml中加入redis的基本配置外,redis怎么用在项目上,一般还需要配置redis key和value的序列化方式,如下:
注解:
其默认的序列化方式为 JdkSerializationRedisSerializer ,这种方式跨语言和可读性都不太好,我们将其切换为 Jackson2JsonRedisSerializer 。
import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import com.ldx.redis.constant.CacheConstant;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.cache.RedisCacheWriter;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;import java.util.HashMap;import java.util.Map;/** * redis配置类 * @author ludangxin * @date 2021/8/11 */@Configurationpublic class RedisConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {//设置不同cacheName的过期时间Map<String,RedisCacheConfiguration> configurations = new HashMap<>(16);// 序列化方式Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = getJsonRedisSerializer();RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);// 默认的缓存时间Duration defaultTtl = Duration.ofSeconds(20L);// 用户模块的缓存时间Duration userTtl = Duration.ofSeconds(50L);// 默认的缓存配置RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() //.entryTtl(defaultTtl) .serializeValuesWith(serializationPair);// 自定义用户模块的缓存配置 自定义的配置可以覆盖默认配置(当前的模块)configurations.put(CacheConstant.USER_CACHE_NAME,RedisCacheConfiguration.defaultCacheConfig() //.entryTtl(userTtl) .serializeValuesWith(serializationPair));return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory)) .cacheDefaults(redisCacheConfiguration) .withInitialCacheConfigurations(configurations) // 事物支持.transactionAware() .build();}@Beanpublic RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<Object,Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = getJsonRedisSerializer();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key采用String的序列化方式template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value序列化方式采用jacksontemplate.setValueSerializer(jsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jsonRedisSerializer);// 支持事物//template.setEnableTransactionSupport(true);template.afterPropertiesSet();return template;}/** * 设置jackson的序列化方式 */private Jackson2JsonRedisSerializer<Object> getJsonRedisSerializer() {Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);redisSerializer.setObjectMapper(om);return redisSerializer;}}
2.4.1 CacheConstant
我们为了防止redis中key的重复,尽量会给不同的数据主体加上不同的前缀,这样我们在查看和统计的时候也方便操作。
** * 缓存key 常量类 * @author ludangxin * @date 2021/8/11 */public interface CacheConstant { /*** 用户cache name*/ String USER_CACHE_NAME = &34;; /*** 用户信息缓存key前缀*/ String USER_CACHE_KEY_PREFIX = &34;; /*** 角色cache name*/ String ROLE_CACHE_NAME = &34;; /*** 角色信息缓存key前缀*/ String ROLE_CACHE_KEY_PREFIX = &34;; /*** 获取角色cache key* @param suffix 后缀* @return key*/ static String getRoleCacheKey(String suffix) {return ROLE_CACHE_NAME + &34; + ROLE_CACHE_KEY_PREFIX + suffix; }}
2.4.2 RedisUtil
import lombok.RequiredArgsConstructor;import org.springframework.data.redis.core.*;import org.springframework.stereotype.Component;import java.util.*;import java.util.concurrent.TimeUnit;/** * spring redis 工具类 * @author ludangxin **/@Component@RequiredArgsConstructor@SuppressWarnings(value = { &34;,&34; })public class RedisUtil {public final RedisTemplate redisTemplate;/** * 缓存基本的对象,Integer、String、实体类等 * @param key 缓存的键值 * @param value 缓存的值 * @return 缓存的对象 */public <T> ValueOperations<String,T> setCacheObject(String key,T value) {ValueOperations<String,T> operation = redisTemplate.opsForValue();operation.set(key,value);return operation;}/** * 缓存基本的对象,Integer、String、实体类等 * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 * @return 缓存的对象 */public <T> ValueOperations<String,T> setCacheObject(String key,T value,Integer timeout,TimeUnit timeUnit) {ValueOperations<String,T> operation = redisTemplate.opsForValue();operation.set(key,value,timeout,timeUnit);return operation;}/** * 获得缓存的基本对象。 * @param key 缓存键值 * @return 缓存键值对应的数据 */public <T> T getCacheObject(String key) {ValueOperations<String,T> operation = redisTemplate.opsForValue();return operation.get(key);}/** * 删除单个对象 * @param key */public void deleteObject(String key) {redisTemplate.delete(key);}/** * 删除集合对象 * @param collection */public void deleteObject(Collection collection) {redisTemplate.delete(collection);}/** * 缓存List数据 * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */public <T> ListOperations<String,T> setCacheList(String key,List<T> dataList) {ListOperations listOperation = redisTemplate.opsForList();if (null != dataList) {int size = dataList.size();for (int i = 0; i < size; i++) {listOperation.leftPush(key,dataList.get(i));}}return listOperation;}/** * 获得缓存的list对象 * @param key 缓存的键值 * @return 缓存键值对应的数据 */public <T> List<T> getCacheList(String key) {List<T> dataList = new ArrayList<T>();ListOperations<String,T> listOperation = redisTemplate.opsForList();Long size = listOperation.size(key);for (int i = 0; i < size; i++) {dataList.add(listOperation.index(key,i));}return dataList;}/** * 缓存Set * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */public <T> BoundSetOperations<String,T> setCacheSet(String key,Set<T> dataSet) {BoundSetOperations<String,T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()) {setOperation.add(it.next());}return setOperation;}/** * 获得缓存的set * @param key * @return */public <T> Set<T> getCacheSet(String key) {Set<T> dataSet = new HashSet<T>();BoundSetOperations<String,T> operation = redisTemplate.boundSetOps(key);dataSet = operation.members();return dataSet;}/** * 缓存Map * @param key * @param dataMap * @return */public <T> HashOperations<String,String,T> setCacheMap(String key,Map<String,T> dataMap) {HashOperations hashOperations = redisTemplate.opsForHash();if (null != dataMap) {for (Map.Entry<String,T> entry : dataMap.entrySet()) {hashOperations.put(key,entry.getKey(),entry.getValue());}}return hashOperations;}/** * 获得缓存的Map * @param key * @return */public <T> Map<String,T> getCacheMap(String key) {Map<String,T> map = redisTemplate.opsForHash().entries(key);return map;}/** * 获得缓存的基本对象列表 * @param pattern 字符串前缀 * @return 对象列表 */public Collection<String> keys(String pattern) {return redisTemplate.keys(pattern);}}
2.5 controller
一:1、先进到redis安装目录的bin目录下,本人的机器安装在这里,即/data/redis/bin 2、在/data/redis/bin目录下可以发现有一个redis-cli文件,执行该文件后即可进入命令为:./redis-cli,由于有密码,要输入密码,命令为。
2.5.1 UserController
2.5.2 RoleController
import com.ldx.redis.entity.SysRole;import com.ldx.redis.service.SysRoleService;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.*;import java.util.List;/** * 角色管理 * @author ludangxin * @date 2021/8/12 */@RestController@RequestMapping(&34;)@RequiredArgsConstructorpublic class RoleController { private final SysRoleService roleService; @GetMapping public List<SysRole> queryAll() {return roleService.queryAll(); } @GetMapping(&34;) public SysRole getUserInfo(@PathVariable Long roleId) {return roleService.getRoleInfo(roleId); } @PostMapping public String add(@RequestBody SysRole role) {roleService.add(role);return &34;; } @PutMapping(&34;) public String update(@PathVariable Long roleId,@RequestBody SysRole role) {roleService.update(roleId,role);return &34;; } @DeleteMapping(&34;) public String del(@PathVariable Long roleId) {roleService.delete(roleId);return &34;; }}
推荐使用项目名:业务名称:时间构成,如:baike:login-name-code:20201228,百科项目登录验证码,与不同的开发人员定义相同时间的概率很低,时间可以按照不同情况定义到天、时、分、秒。
2.6 service.impl
2.6.1 UserServiceImpl
优雅的使用redis注解实现对数据的缓存
@Cacheable:unless :当 unless 成立时则不缓存。这里判断 size 主要是不想将空值存入redis。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.ldx.redis.constant.CacheConstant;import com.ldx.redis.entity.SysUser;import com.ldx.redis.mapper.SysUserMapper;import com.ldx.redis.service.SysUserService;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.cache.annotation.CacheConfig;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.Cacheable;import org.springframework.cache.annotation.Caching;import org.springframework.stereotype.Service;import java.util.List;/** * 用户管理实现 * @author ludangxin * @date 2021/8/11 */@Slf4j@Service@RequiredArgsConstructor@CacheConfig(cacheNames = CacheConstant.USER_CACHE_NAME)public class SysUserServiceImpl implements SysUserService { private final SysUserMapper userMapper; @Override @Cacheable(key = &39;&34;all&34;,unless = &result.size() == 0&34;查询全部用户信息~&34;&34; + CacheConstant.USER_CACHE_KEY_PREFIX + &39; + 34;,unless = &result == null&34;查询用户:{} 详情&34;&34; + CacheConstant.USER_CACHE_KEY_PREFIX + &39;&34;新增用户:{}&34;&34; + CacheConstant.USER_CACHE_KEY_PREFIX + &39;&34;&34; + CacheConstant.USER_CACHE_KEY_PREFIX + &39; + 34;) }) public void update(Long userId,SysUser user) {log.debug(&34;,user.getNickName());user.setId(userId);userMapper.updateById(user); } @Override @Caching(evict = {@CacheEvict(key = &39;&34;all&34;),@CacheEvict(key = &39;&34;&userId&34;删除用户:{}",userId);userMapper.deleteById(userId); }}
2.6.2 SysRoleServiceImpl
使用redis工具类实现对数据的缓存。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.ldx.redis.constant.CacheConstant;import com.ldx.redis.entity.SysRole;import com.ldx.redis.mapper.SysRoleMapper;import com.ldx.redis.service.SysRoleService;import com.ldx.redis.util.RedisUtil;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import org.springframework.util.CollectionUtils;import java.util.Collections;import java.util.List;import java.util.Objects;/** * 角色管理 * @author ludangxin * @date 2021/8/11 */@Slf4j@Service@RequiredArgsConstructorpublic class SysRoleServiceImpl implements SysRoleService { private final SysRoleMapper roleMapper; private final RedisUtil redisUtil; String allKey = CacheConstant.getRoleCacheKey(&34;); @Override public List<SysRole> queryAll() {List<SysRole> roles = redisUtil.getCacheList(allKey);if(!CollectionUtils.isEmpty(roles)) { return roles;}log.debug(&34;);LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();List<SysRole> sysRoles = roleMapper.selectList(queryWrapper);if(CollectionUtils.isEmpty(sysRoles)) { return Collections.emptyList();}redisUtil.setCacheList(allKey,sysRoles);return sysRoles; } @Override public SysRole getRoleInfo(Long roleId) {String roleCacheKey = CacheConstant.getRoleCacheKey(String.valueOf(roleId));SysRole role = redisUtil.getCacheObject(roleCacheKey);if(Objects.nonNull(role)) { return role;}log.debug(&34;,roleId);SysRole sysRole = roleMapper.selectById(roleId);if(Objects.isNull(sysRole)) { return null;}redisUtil.setCacheObject(roleCacheKey,sysRole);return sysRole; } @Override public void add(SysRole role) {log.debug(&34;,role.getName());roleMapper.insert(role);redisUtil.deleteObject(allKey); } @Override public void update(Long roleId,SysRole role) {log.debug(&34;,role.getName());String roleCacheKey = CacheConstant.getRoleCacheKey(String.valueOf(roleId));role.setId(roleId);roleMapper.updateById(role);// 更新缓存redisUtil.setCacheObject(roleCacheKey,role);// 清除缓存redisUtil.deleteObject(allKey); } @Override public void delete(Long roleId) {log.debug(&34;,roleId);roleMapper.deleteById(roleId);// 清除缓存redisUtil.deleteObject(CacheConstant.getRoleCacheKey(String.valueOf(roleId)));redisUtil.deleteObject(allKey); }}
2.7 启动测试
这里只测试了user模块(都测试并且贴图会显得篇幅太长且繁琐),role模块本人测试后结果正确。
查询列表:
调用接口返回全部数据并缓存完成,再次调用无查询日志输出,符合预期。
接口调用:
查看缓存:
查看用户详情:
接口调用返回用户详情信息并缓存完成,再次调用无查询日志输出,符合预期。
接口调用:
查看缓存:
更新数据:
接口调用返回更新成功,并且查看全部的缓存被清除。符合预期。
接口调用:
查看缓存:
3. 内置缓存注解
3.1 @CacheConfig
@Cacheable()里面都有一个value=“xxx”的属性,这显然如果方法多了,写起来也是挺累的,如果可以一次性
3.2 @Cacheable
3.3 @CachePut
3.4 @CacheEvict
@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空。
/ 清空当前cache name下的所有key@CachEvict(allEntries = true)
3.5 @Caching
@Caching可以使注解组合使用,比如根据id查询用户信息,查询完的结果为{key = id,value = userInfo},但我们现在为了方遍,想用用户的手机号,邮箱等缓存对应用户的信息,这时候我们就要使用@Caching。例: