Spring 从3.1版开始,Spring Framework提供了对现有Spring应用程序透明地添加缓存的支持。与事务 支持类似,缓存抽象允许一致地使用各种缓存解决方案,而对代码的影响最小。从Spring4.1 开始,JSR107提供了注解开发模式,以及自定义选项的支持,缓存技术得到显著的改善。Spring官网对本章介绍链接地址
Spring使用的缓存抽象也是基于JSR107规范,我们可以看一下Spring与JSR107的比较;官网有详细的介绍官网地址
Spring | JSR-107 | Remark |
---|---|---|
|
|
@CacheResult 无论缓存的内容如何,都可以缓存特定的异常并强制执行该方法。 |
|
|
当Spring使用方法调用的结果更新缓存时。 |
|
|
清除缓存,指定清除某一项 |
|
|
清除所有,Spring还是使用@CacheEvict注解,只需将属性allEntries设置为true即可 |
|
|
缓存配置 |
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口实现缓存抽象,
。Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
。Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
如果我们想要使用spring的缓存,就需要来实现相应的CacheManager(缓存管理器)来管理相应的Cache组件,缓存接口来实现相应的缓存组件。有了缓存组件,我们才能去通过注解,操作数据将数据缓存到相应的地方。当然我们这里Spring已经默认帮我们实现了一套默认的缓存管理器和缓存组件,我们可以直接使用Spring缓存。下面我们通过一个小的案例来看一下,怎么在SpringBoot中使用Spring的缓存技术(基于注解的形式)。
需要分成哪些步骤:
首先创建一个SpringBoot项目(源码已经传到github仓库中地址在最后会贴出,如有需要可以见到查看一下);项目整合了Mybatis通过SpringBoot的自动配置,使用Mybatis的注解版,方便演示。
1.在启动类中开启缓存注解@EnableCaching
@MapperScan(value = "com.cache.mapper")
@SpringBootApplication
//开启缓存注解
@EnableCaching
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
接下来我们domain,service,mapper,controller 下面节选部分代码,
domain
public class User {
private Integer id;
private String userName;
private String userAge;
service 和 serviceImpl
public interface UserService {
User findUserById(Integer id);
User updateUser(User user);
void deleteUser(Integer id);
User findUserByUser(String userName);
}
ServiceImpl
package com.cache.service;
import com.cache.domain.User;
import com.cache.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* @param id
* @return
* @Cacheable 注解属性:主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
* cacheNames/value 使用这两个属性一样效果一样,指定缓存组件名字,将方法的返回结果放在哪个缓存中,数组的方式可以制定多个缓存
* key 默认是参数的值,也可以自己指定,可以使用spEL表达式指定key的值。缓存在存放数据时,是根据这个key来标注。
* keyGenerator key的生成器,可以在自己来实现key的生成策略;指定key的生成器组件id key/keyGenerator两者二选一
* cacheManager 指定缓存管理器,或cacheResolver指定获取解析器;
* condition 指定条件满足才缓存;也可以使用多个条件,用and连接;
* unless 除非,当unless指定的条件为true时候,不缓存
* sync 是否异步 (默认是false,在使用异步情况下,unless就不支持了)
*/
@Cacheable(cacheNames = "user")
@Override
public User findUserById(Integer id) {
User user = userMapper.getUserById(id);
return user;
}
/**
* @param user
* @return
* @CachePut 方法既调用,结果也会保存,修改了某条数据,缓存也会跟着更新(需要KEY保持一致)
* value 既保存数据时指定的cacheName/value
* key 既查询时,在进行数据保存时的key,这里取值有两种方式
* 1. 可以通过传入的对象的属性方式拿到更改对象的key #user.id
* 2. 也可以通过返回值的值来进行绑定ker的值 #result.id
*/
@CachePut(value = "user", key = "#user.id")
@Override
public User updateUser(User user) {
userMapper.updateUser(user);
return user;
}
/**
* @cacheEvict 清除缓存
* allEntity 清除所有员工,默认的是false,如果设置为true,会将缓存中的所有数据全部清空
* beforeInvocation 缓存是否在方法执行之前,默认的是false;
* false,方法执行之后之后执行;
* true,方法执行之前执行;(缓存必定被清除,无论方法时候有错误)
*/
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Integer id) {
System.out.println("清空ID为" + id + "的缓存");
}
/**
* @param userName
* @return Caching 组合包含了Cacheable、CachePut、CacheEvict 可以以数组的形式传入多个值
*
* 注解的value值可以在类上,统一使用cacheConfig(cacheName="user")形式替换,
* 就无需再一个一个的写在具体的注解上
*/
@Caching(cacheable = {
@Cacheable(value = "user", key = "#userName")
}, put = {@CachePut(value = "user", key = "#result.id")}
)
public User findUserByUser(String userName) {
return userMapper.getUserByName(userName);
}
}
mapper
public interface UserMapper {
@Select("select * from USER where id=#{id}")
User getUserById(Integer id);
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into USER (USER_NAME,USER_AGE) VALUES (#{userName},#{userAge})")
int insertUser(User user);
@Update("update USER SET USER_NAME = #{userName}, USER_AGE = #{userAge} where ID = #{id}")
int updateUser(User user);
@Delete("delete from user where ID = #{id}")
int deleteUser(Integer id);
@Select("select * from USER where USER_NAME=#{userName}")
User getUserByName(String userName);
}
controller
@RestController
public class UserController {
@Autowired
UserMapper userMapper;
@Autowired
UserService userService;
@GetMapping("/findUserById/{id}")
public User userfinById(@PathVariable("id") Integer id) {
User user = userService.findUserById(id);
return user;
}
@GetMapping("/insertUser")
public User save(User user) {
int total = userMapper.insertUser(user);
return user;
}
@GetMapping("/updateUser")
public User update(User user) {
User user1 = userService.updateUser(user);
return user1;
}
@GetMapping("/deleteUser")
public String deleteUser(Integer id) {
userService.deleteUser(id);
return "删除成功";
}
@GetMapping("/findUserByName")
public User findUserByName(String userName) {
User user = userService.findUserByUser(userName);
return user;
}
}
注解@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig都在serviceImpl方法上标注,每个注解的作用,以及注解各个属性的设置;
这里补充一个keyGenerator,生成key的内容,首先key,最简单的使用就是无需指定默认使我们传入参数的值,也可以通过SpEL语法来制定相应的key。可以取方法名来用作key的值,也可以是方法的name;下面还有其他属性详细的使用以表格的形式展示给大家;
Cache SpEL available metadata
名字 |
位置 描述 示例 |
methodName |
root object 当前被调用的方法名 #root.methodName |
method |
root object 当前被调用的方法 #root.method.name |
target |
root object 当前被调用的目标对象 #root.target |
targetClass |
root object 当前被调用的目标对象类 #root.targetClass |
args |
root object 当前被调用的方法的参数列表 #root.args[0] |
caches |
root object 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", #root.caches[0].name "cache2"})),则有两个cache |
argument name |
evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的 #iban 、 #a0 、 #p0 形式,0代表参数的索引; |
result |
evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如 #result‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false) |
我们还可以自己指定keyGenerator属性,属性值是我们自定义的key的生成策略;通过向容器中添加自定义配置就可以使用相应的keyGenerator;这里简单写了一个;
@Configuration
public class MyCacheConfig {
/**
* 默认组件id就是方法名,可以自己自定义
* @return
*/
@Bean("myCacheKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
//返回方法名+参数
return method.getName() + Arrays.asList(objects).toString();
}
};
}
}
底层是如何实现的呢?我们可以通过后面执行过程来解析为什么我们配置了自己的KeyGenerator的生成策略,就会起作用;
自动配置是通过CacheAutoConfiguration自动配置类来实现;可以通过查看源码的方式来看默认的是如何获取的。
后面的整合Redis会在后面介绍;本次的代码 githu仓库地址