當咱們對redis的基本知識有必定的瞭解後,咱們再經過實戰的角度學習一下在SpringBoot環境下,如何優雅的使用redis。java
咱們經過使用SpringBoot內置的Redis註解(文章最後有解釋)來操做User相關的信息,mysql
再經過Redis工具類的方式操做Role相關信息來全面的學習Redis的使用。web
嫌篇幅太長的 能夠直接跳到2.6查看具體邏輯便可。redis
結構說明:spring
├── src │ └── main │ ├── java │ │ └── com │ │ └── ldx │ │ └── redis │ │ ├── RedisApplication.java # 啓動類 │ │ ├── config │ │ │ └── RedisConfig.java # redis 配置類 │ │ ├── constant │ │ │ └── CacheConstant.java # 緩存key常量類 │ │ ├── controller │ │ │ ├── RoleController.java # 角色管理控制器 │ │ │ └── UserController.java # 用戶管理控制器 │ │ ├── entity │ │ │ ├── SysRole.java # 角色entity │ │ │ └── SysUser.java # 用戶entity │ │ ├── mapper │ │ │ ├── SysRoleMapper.java # 角色持久層 │ │ │ └── SysUserMapper.java # 用戶持久層 │ │ ├── service │ │ │ ├── SysRoleService.java # 角色接口層 │ │ │ ├── SysUserService.java # 用戶接口層 │ │ │ └── impl │ │ │ ├── SysRoleServiceImpl.java # 角色接口實現層 │ │ │ └── SysUserServiceImpl.java # 用戶接口實現層 │ │ └── util │ │ └── RedisUtil.java # redis 工具類 │ └── resources │ └── application.yaml # 系統配置文件 └── pom.xml # 依賴管理
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ldx</groupId> <artifactId>redis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>redis</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--spring-web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- lettuce pool --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <!-- mysql驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- lombok 工具包 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 type: com.zaxxer.hikari.HikariDataSource # redis 配置 redis: # 地址 host: localhost # 端口,默認爲6379 port: 6379 # 密碼 password: # 鏈接超時時間 timeout: 10s lettuce: pool: # 鏈接池中的最小空閒鏈接 min-idle: 0 # 鏈接池中的最大空閒鏈接 max-idle: 8 # 鏈接池的最大數據庫鏈接數 max-active: 8 # #鏈接池最大阻塞等待時間(使用負值表示沒有限制) max-wait: -1ms mybatis-plus: # 設置Mapper接口所對應的XML文件位置,若是你在Mapper接口中有自定義方法,須要進行該配置 mapper-locations: classpath*:mapper/*.xml # 設置別名包掃描路徑,經過該屬性能夠給包中的類註冊別名 type-aliases-package: com.ldx.redis.entity configuration: # 控制檯sql打印 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日誌配置 logging: level: com.ldx.redis.service.impl: debug org.springframework: warn
@EnableCaching
:激活緩存支持sql
@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 = "com.ldx.redis.mapper") @SpringBootApplication public class RedisApplication { public static void main(String[] args) { SpringApplication.run(RedisApplication.class, args); } }
咱們除了在application.yaml中加入redis的基本配置外,通常還須要配置redis key和value的序列化方式,以下:apache
註解:json
其默認的序列化方式爲JdkSerializationRedisSerializer
,這種方式跨語言和可讀性都不太好,咱們將其切換爲Jackson2JsonRedisSerializer
。緩存
可使用entryTtl()
爲對應的模塊設置過時時長。
redisTemplate:參考redisTemplate()
。
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 */ @Configuration public class RedisConfig { @Bean public 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(); } @Bean public 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序列化方式採用jackson template.setValueSerializer(jsonRedisSerializer); // hash的value序列化方式採用jackson template.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; } }
咱們爲了防止redis中key的重複,儘可能會給不一樣的數據主體加上不一樣的前綴,這樣咱們在查看和統計的時候也方便操做。
/** * 緩存key 常量類 * @author ludangxin * @date 2021/8/11 */ public interface CacheConstant { /** * 用戶cache name */ String USER_CACHE_NAME = "user_cache"; /** * 用戶信息緩存key前綴 */ String USER_CACHE_KEY_PREFIX = "user_"; /** * 角色cache name */ String ROLE_CACHE_NAME = "role_cache"; /** * 角色信息緩存key前綴 */ String ROLE_CACHE_KEY_PREFIX = "role_"; /** * 獲取角色cache key * @param suffix 後綴 * @return key */ static String getRoleCacheKey(String suffix) { return ROLE_CACHE_NAME + "::" + ROLE_CACHE_KEY_PREFIX + suffix; } }
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 = { "unchecked", "rawtypes" }) 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); } }
import com.ldx.redis.entity.SysUser; import com.ldx.redis.service.SysUserService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 用戶管理 * @author ludangxin * @date 2021/8/11 */ @RestController @RequestMapping("user") @RequiredArgsConstructor public class UserController { private final SysUserService userService; @GetMapping public List<SysUser> queryAll() { return userService.queryAll(); } @GetMapping("{userId}") public SysUser getUserInfo(@PathVariable Long userId) { return userService.getUserInfo(userId); } @PostMapping public String add(@RequestBody SysUser user) { userService.add(user); return "新增成功~"; } @PutMapping("{userId}") public String update(@PathVariable Long userId, @RequestBody SysUser user) { userService.update(userId, user); return "更新成功~"; } @DeleteMapping("{userId}") public String del(@PathVariable Long userId) { userService.delete(userId); return "刪除成功~"; } }
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("role") @RequiredArgsConstructor public class RoleController { private final SysRoleService roleService; @GetMapping public List<SysRole> queryAll() { return roleService.queryAll(); } @GetMapping("{roleId}") public SysRole getUserInfo(@PathVariable Long roleId) { return roleService.getRoleInfo(roleId); } @PostMapping public String add(@RequestBody SysRole role) { roleService.add(role); return "新增成功~"; } @PutMapping("{roleId}") public String update(@PathVariable Long roleId, @RequestBody SysRole role) { roleService.update(roleId, role); return "更新成功~"; } @DeleteMapping("{roleId}") public String del(@PathVariable Long roleId) { roleService.delete(roleId); return "刪除成功~"; } }
優雅的使用redis註解實現對數據的緩存
@Cacheable:unless
:當unless
成立時則不緩存。這裏判斷size
主要是不想將空值存入redis。
CacheConstant.USER_CACHE_KEY_PREFIX + "' + #userId"
:其key = 指定前綴 + 當前方法實參(userId)。
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 = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "all'", unless = "#result.size() == 0") public List<SysUser> queryAll() { log.debug("查詢所有用戶信息~"); LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); return userMapper.selectList(queryWrapper); } @Override @Cacheable(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "' + #userId", unless = "#result == null") public SysUser getUserInfo(Long userId) { log.debug("查詢用戶:{} 詳情", userId); return userMapper.selectById(userId); } @Override @CacheEvict(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "all'") public void add(SysUser user) { log.debug("新增用戶:{}", user.getNickName()); userMapper.insert(user); } @Override @Caching(evict = {@CacheEvict(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "all'"), @CacheEvict(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "' + #userId") }) public void update(Long userId, SysUser user) { log.debug("更新用戶:{}", user.getNickName()); user.setId(userId); userMapper.updateById(user); } @Override @Caching(evict = {@CacheEvict(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "all'"), @CacheEvict(key = "'" + CacheConstant.USER_CACHE_KEY_PREFIX + "' + #userId") }) public void delete(Long userId) { log.debug("刪除用戶:{}", userId); userMapper.deleteById(userId); } }
使用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 @RequiredArgsConstructor public class SysRoleServiceImpl implements SysRoleService { private final SysRoleMapper roleMapper; private final RedisUtil redisUtil; String allKey = CacheConstant.getRoleCacheKey("all"); @Override public List<SysRole> queryAll() { List<SysRole> roles = redisUtil.getCacheList(allKey); if(!CollectionUtils.isEmpty(roles)) { return roles; } log.debug("查詢所有角色信息~"); 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("查詢角色:{} 詳情", 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("新增角色:{}", role.getName()); roleMapper.insert(role); redisUtil.deleteObject(allKey); } @Override public void update(Long roleId, SysRole role) { log.debug("更新角色:{}", 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("刪除角色:{}", roleId); roleMapper.deleteById(roleId); // 清除緩存 redisUtil.deleteObject(CacheConstant.getRoleCacheKey(String.valueOf(roleId))); redisUtil.deleteObject(allKey); } }
這裏只測試了user模塊(都測試而且貼圖會顯得篇幅太長且繁瑣),role模塊本人測試後結果正確。
查詢列表:
調用接口返回所有數據並緩存完成,再次調用無查詢日誌輸出,符合預期。
接口調用:
查看緩存:
查看用戶詳情:
接口調用返回用戶詳情信息並緩存完成,再次調用無查詢日誌輸出,符合預期。
接口調用:
查看緩存:
更新數據:
接口調用返回更新成功,而且查看所有的緩存被清除。符合預期。
接口調用:
查看緩存:
@Cacheable()裏面都有一個value=「xxx」的屬性,這顯然若是方法多了,寫起來也是挺累的,若是能夠一次性聲明完 那就省事了, 因此,有了@CacheConfig這個配置,@CacheConfig is a class-level annotation that allows to share the cache names
,若是你在你的方法寫別的名字,那麼依然以方法的名字爲準。
@Cacheable(value="myCache"),這個註釋的意思是,當調用這個方法的時候,會從一個名叫myCache 的緩存中查詢,若是沒有,則執行實際的方法(即查詢數據庫),並將執行的結果存入緩存中,不然返回緩存中的對象。
@CachePut 的做用 主要針對方法配置,可以根據方法的請求參數對其結果進行緩存,和 @Cacheable 不一樣的是,它每次都會觸發真實方法的調用。
@CachEvict 的做用 主要針對方法配置,可以根據必定的條件對緩存進行清空。
// 清空當前cache name下的全部key @CachEvict(allEntries = true)
@Caching可使註解組合使用,好比根據id查詢用戶信息,查詢完的結果爲{key = id,value = userInfo},但咱們如今爲了方遍,想用用戶的手機號,郵箱等緩存對應用戶的信息,這時候咱們就要使用@Caching。例:
@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) public User getUserInfo(User user){ ... return user; }