- 学习SpringBoot提供的Cacheable注解的使用;
我们使用SpringBoot自带的Lettuce缓存框架,通过上一节已经完成了对Lettuce的集成,测试只是做了手动写代码将我们的对象写入到redis中了,也读了出来,缓存实际也就是干存取的工作。你项目中要这么使用也没毛病。
但牛逼的程序猿就是要把懒发挥到极致,Cacheable就是这么产生的。
Cacheable.java源码:
public @interface Cacheable {
//缓存的名称
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// key值,可以从参数中获取
//key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
通过在方法上添加 @Cacheable 注解,框架可以自动帮我们把查询结果缓存起来,我们也无需特地去使用缓存api去取。存和读操作透明了,
- value:属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组
- key:属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key
- condition:属性指定发生的条件。有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。
- sync:缓存的同步 。在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。
key:内置了root对象,可获取方法调用相关参数作为 key值:
名称 | 描述 | 示例 |
---|---|---|
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
methodName | 当前方法名 | #root.methodName |
不使用Cacheable时缓存的用法是这样的:
/**
* 这是手动存取缓存,存和取缓存占了很大代码量,而且入侵了我们的代码,非常不友好。
* @param username
* @return
*/
public Object getUserInfo(String username) {
if (redisTemplate.opsForValue().get(username) == null) {
System.out.println("未获取到缓存,新建用户信息.............");
Map<String, Object> user = new HashMap<>();
user.put("username", username);
user.put("usercode", "zhangsan");
user.put("sex", "男");
user.put("createtime", new Date());
redisTemplate.opsForValue().set(username, user);
}
return redisTemplate.opsForValue().get(username);
}
key的用法
/**
* 使用注解时,只有一行代码,业务代码也没有被入侵,简直不要太爽。
* @param username
* @return
*/
@Cacheable(value = "USER_INFO", key = "#username")
public Object getUserInfo3(String username) {
System.out.println("未获取到缓存,从数据库获取.............");
Map<String, Object> user = new HashMap<>();
user.put("username", username);
user.put("usercode", "zhangsan");
user.put("sex", "男");
user.put("createtime", DateUtils.format(new Date()));
return user;
}
/**
* 参数为自定义对像时,获取对象值为key
*
* @param user
* @return
*/
// @Cacheable(value = "USER_INFO", key = "#user.userId")
// @Cacheable(value = "USER_INFO", key = "#p0.userId")
@Cacheable(value = "USER_INFO", key = "#p0.userId.concat('-').concat(#p0.username)")
public Object getUserInfo3(User user) {
System.out.println("未获取到缓存,从数据库获取.............");
Map<String, Object> userMap = new HashMap<>();
userMap.put("userId", user.getUserId());
userMap.put("username", "张三");
userMap.put("usercode", "zhangsan");
userMap.put("sex", "男");
userMap.put("createtime", DateUtils.format(new Date()));
}
Redis服务端存储结构:
condition用法:
/**
* condition 的用法
*
* @param userId
* @return
*/
@Cacheable(value = {"USER_INFO"}, key = "#userId", condition = "#userId%2==0")
public Object conditionTest(Long userId) {
System.out.println("未获取到缓存,从数据库获取.............");
Map<String, Object> userMap = new HashMap<>();
userMap.put("userId", userId);
userMap.put("username", "张三" + userId);
userMap.put("usercode", "zhangsan" + userId);
userMap.put("sex", "男");
userMap.put("createtime", DateUtils.format(new Date()));
return userMap;
}
@Test
public void conditionTest() {
System.out.println(userService.conditionTest(22L));
System.out.println(userService.conditionTest(22L));
System.out.println(userService.conditionTest(23L));
System.out.println(userService.conditionTest(23L));
}
输出结果:
id=22时,第二次是从缓存中获取结果;
id=23时,每次都是新建对象。
sync用法
/**
* sync 的用法
*
* @param userId
* @return
*/
@Cacheable(value = {"USER_INFO"}, key = "#userId", sync = true)
public Object syncTest(Long userId) {
System.out.println("未获取到缓存,从数据库获取.............");
Map<String, Object> userMap = new HashMap<>();
userMap.put("userId", userId);
userMap.put("username", "张三" + userId);
userMap.put("usercode", "zhangsan" + userId);
userMap.put("sex", "男");
userMap.put("createtime", DateUtils.format(new Date()));
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000L);
System.out.println("处理中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return userMap;
}
@Test
public void testSync() {
new Thread(()->{
System.out.println(userService.syncTest(15L));
}).start();
new Thread(()->{
System.out.println(userService.syncTest(15L));
}).start();
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出结果:
1、但多线程情况下,查询参数相同的情况下,由于设置了sync=true,所以出现以线程等待查询结果返回之后,才从缓存中获取值。如此便节省了服务器计算资源。