前言
前面有介绍过spring
整合redis和redis的一些注意点,在实际开发中,spring cache方便简化的缓存操作的大部分内容。通过注解的方式实现缓存。
阅读前推荐先阅读:redis缓存介绍。和springboot整合redis
缓存抽象的核心是将缓存应用于Java方法
,从而根据缓存中可用的信息减少执行次数。也就是说,每次调用目标方法时,抽象都会应用一种缓存行为,该行为检查该方法是否已针对给定参数执行。
- 如果已执行,则返回缓存的结果,而不必执行实际方法。
- 如果该方法尚未执行,则执行该方法,并将结果缓存并返回给用户,以便下次调用该方法时,返回缓存的结果。
- 这样,对于给定的一组参数,昂贵的方法(无论是CPU还是IO)只能执行一次,并且重用结果而不必再次实际执行该方法。
在缓存框架出现之前,要么公司自己写缓存框架造轮子
,要么在每个service中手动修改调用redis进行存储或者更新(删除)
.一旦业务逻辑代码有变
,将造成不小的维护负担
。并且代码量也很大,使得项目看起来很臃肿!
而spring cache的出现用于简化对缓存的操作,你只需要配置缓存的cacheManager,然后按照一些简单的序列化配置,在service层即可使用注解的形式对项目逻辑无侵入
的调用缓存。
方法介绍
@Cacheable:
触发缓存填充。即:如果有缓存从缓存中直接拿数据
(就不走service
了),如果没缓存那么就执行service
。将service中的值传到缓存中(执行service
)。
- 多个名称:缓存可能有
一个名字
或者多个名字
,那么你可以@Cacheable("books")
或者@Cacheable({"books", "isbns"})
表示。 - 自定义key:大家对同一个接口进行查询,可能有多个参数,那么你可以自定义key。类似
@Cacheable(value = "getuser",key = "#username")
这样写法,这个规则和mybatis注解很像,但也有点区别也可以自行拼凑。 - 条件缓存:有些数据你若不想所有都想走缓存,当然也可以设置条件缓存,比如你若想让两个字的姓名缓存,那么你可以
@Cacheable(value = "getuser",key = "#username",condition="#username.length() < 3")
这样写法 - 上锁:
@Cacheable(value = "getuser",key = "#username",sync = true)
对有些数据,你可能不想让他并发执行,那么可以假设sync关键字。当然,如果是分布式或者多台服务器需要考虑分布式锁实现。
@Cacheable(value = "getalluser") public List<user> getalluser() { /* 假设数据从数据库查出啦。有这么两条 */ user user1=new user("bigsai","12345","man"); user user2=new user("给我star","谢谢","man"); List<user>list=new ArrayList<>(); list.add(user1); list.add(user2); return list; } //获取user 如果没有缓存将执行log @Cacheable(value = "getuser",key = "#username") public user getuserbyname(String username) { //假设该数据从mysql查询结果如下 user user=new user(username,"123","women"); logger.info("执行方法cacheable,从mysql查询"); return user; }
@CacheEvict:
触发缓存逐出。即删除缓存。执行之后将删除缓存。
- 用法和前面相似
@CacheEvict(value = "getuser",key = "#username")
但是若想删除getuser下所有缓存目录,那么加个参数@CacheEvict(value = "getuser",key = "#username",allEntries = true)其中allEntries就是删除所有的意思。当需要清除整个缓存区域时,此选项会派上用场。而不是逐出每个条目。
@CachePut:
更新缓存而不会干扰方法执行。即:不管有没有缓存。每次都执行方法,将方法得到的数据更新到缓存之中。起到update的功能。
- 需要注意的是,在更新过程中可能有并发的存在。你可以
设置锁
.参考Cacheable用法
@Caching:
重新组合要应用于方法的多个缓存操作。
- 有时,需要指定
相同类型
(例如@CacheEvict或 @CachePut)的多个注释- 例如,因为不同高速缓存之间的条件或键表达式不同。@Caching允许多个嵌套 @Cacheable,@CachePut和@CacheEvict注解相同的方法来使用。以下示例使用两个@CacheEvict注释:@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
- 此操作可能是一个方法操作更新、删除不同的缓存。具体使用需要考虑情景。
@CacheConfig:
在类级别共享一些常见的缓存相关设置。
- 此方法针对同一个类中一些共同操作,简便操作和代码量。
- 到目前为止,我们已经看到缓存操作提供了
许多自定义选项
,您可以为每个操作设置
这些选项。但是,如果某些自定义选项适用于该类
的所有操作,则它们可能会很繁琐
。例如,指定用于类的每个高速缓存操作的高速缓存的名称可以由单个类级定义替换。这是@CacheConfig 发挥作用的地方。以下示例用于@CacheConfig设置缓存的名称:
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
具体整合
step1:创建springboot工程,省略部分截图
step2:添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
step3: 配置application.properties
spring.redis.host=127.0.0.1 spring.redis.password= spring.redis.port= 6379 spring.redis.timeout=10000 spring.cache.type=redis logging.level.com.redisCache=debug spring.redis.database=3 spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-wait=-1 spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0
step4: config目录下建立配置文件(缓存注入对象配置,以及序列化相关配置,和前文redis差不多)
- 这里面需要注意的是里面有关于redis的一些配置。在项目中防止多个项目公用一个redis可以在redis前面加个
前缀
(通常是项目名)。 过期时间
一定要设置,并且过期策略还需要根据项目需求具体踩坑设置。
package com.redisCache.config; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.io.Serializable; import java.time.Duration; import java.util.*; @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisConfiguration { @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory redisConnectionFactory) { RedisTemplate<String, Serializable> template = new RedisTemplate<>(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean(name = "cacheManager") @Primary public CacheManager cacheManager( RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() // .entryTtl(Duration.ofSeconds(50))设置过期时间 .disableCachingNullValues() .computePrefixWith(cacheName -> "rediscache".concat(":").concat(cacheName).concat(":"))//rediscache可以改成你的项目名 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(cacheConfiguration) .build(); } }
step5:创建pojo对象和controller
pojo:
package com.redisCache.pojo; import java.io.Serializable; public class user implements Serializable { private String name; private String password; private String sex; public user(String name,String password,String sex) { this.name=name; this.password=password; this.sex=sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
service:
package com.redisCache.service; import com.redisCache.pojo.user; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class redisService { Logger logger= LoggerFactory.getLogger(redisService.class); @Cacheable(value = "getalluser") public List<user> getalluser() { /* 假设数据从数据库查出啦。有这么两条 */ user user1=new user("bigsai","12345","man"); user user2=new user("给我star","谢谢","man"); List<user>list=new ArrayList<>(); list.add(user1); list.add(user2); return list; } //获取user 如果没有缓存将执行log @Cacheable(value = "getuser",key = "#username") public user getuserbyname(String username) { //假设该数据从mysql查询结果如下 user user=new user(username,"123","women"); logger.info("执行方法cacheable,从mysql查询"); return user; } //更新user。每次都执行 @CachePut(value = "getuser",key = "#username") public user updateuser(String username,String password) { //假设更新用户账号密码re user user=new user(username,"123","women"); user.setPassword(password); logger.info("执行方法cacheput,再数据库更新返回"); return user; } //删除缓存,其中condition可加可不加,本句意思是只有当姓名为bigsai @CacheEvict(value = "getuser",key = "#username",allEntries = true) public String deleteuser(String username) { return "移除成功"; } }
controller:
package com.redisCache.controller; import com.redisCache.pojo.user; import com.redisCache.service.redisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class redisController { private final static Logger log= LoggerFactory.getLogger(redisController.class); @Autowired(required = false) redisService redisService; @GetMapping("getalluser") public List<user>getalluser() { return redisService.getalluser(); } @GetMapping("getuser/{username}") public user getuser(@PathVariable String username) { return redisService.getuserbyname(username); } @GetMapping("updateuser/{username}/{password}") public user updateuser(@PathVariable String username,@PathVariable String password) { return redisService.updateuser(username,password); } @GetMapping("deleteuser/{username}") public String deleteuser(@PathVariable String username) { return redisService.deleteuser(username); } }
step6:在启动类加上允许缓存注解
测试与总结
getalluser:
getuser:
update:更新上一条
delete:整个移除(前面讲到allentries参数作用,不妨自己测试)
- 可以看得到缓存的一些操作
正常完成
了 - 上文只是一个简单的整合过程,具体使用还需要自己踩坑才行。而缓存的深度远远不是这么简单,还需要自己挖掘。
- 项目完整github地址。不吝啬star的大哥求个star。
- 最后,欢迎关注
个人公众号
:bigsai 关注后回复java
有精选资料一份!