【1】JSR107规范
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
Cache:是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
Entry:是一个存储在Cache中的key-value对。
Expiry :每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
SpringBoot中添加javax.cache依赖:
<dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency>
项目整合缓存结构示意图如下:
实际项目应用中,很少使用JSR107进行整合,通常使用Spring的缓存抽象 !
【2】Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术,
并支持使用JCache(JSR-107)注解简化我们开发。
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCache , ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过。如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点:
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
【3】SpringBoot整合Spring缓存示例
① 搭建基本环境
基本环境如下:pom文件引入依赖,配置MyBatis数据源,编写bean、service、controller和Mapper注解类。
依赖示例如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
两个bean属性如下:
EmployeeMapper 注解示例(DepartmentMapper类同):
public interface EmployeeMapper { @Select("select * from employee where id=#{id}") public Employee getEmpById(Integer id); @Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})") public void insertEmp(Employee employee); @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}") public void updateEmp(Employee employee); @Delete("delete from employee where id = #{id}") public void deleteEmpById(Integer id); }
EmployeeService示例如下:
@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee empById = employeeMapper.getEmpById(id); return empById; } }
EmployeeController示例如下:
@RestController public class EmployeeController { @Autowired EmployeeService employeeService; @GetMapping("/emp/{id}") public Employee getEmp(@PathVariable("id") Integer id){ return employeeService.getEmp(id); } }
数据源配置如下(根据需要自定义配置):
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 开启驼峰命名匹配规则 mybatis.configuration.map-underscore-to-camel-case=true #mybatis.configuration.cache-enabled=true
主程序如下:
@MapperScan(value = "com.web.springboot.mapper") //不用在每个mapper上添加@Mapper注解 @SpringBootApplication public class SpringBoot01CacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot01CacheApplication.class, args); } }
② 使用@EnableCaching和@Cacheable测试
@EnableCaching开启基于注解的Spring Cache,@Cacheable作用于方法上,标明该方法结果可被缓存。
源码示例如下:
@MapperScan(value = "com.web.springboot.mapper") @SpringBootApplication // 主程序添加@EnableCaching注解 @EnableCaching public class SpringBoot01CacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot01CacheApplication.class, args); } } // 具体方法上添加@Cacheable注解 @Cacheable(cacheNames = "emp") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee empById = employeeMapper.getEmpById(id); return empById; }
测试,连续两次获取id为1的Employee,控制台输出如下:
即,只进行了一次查询,第二次从缓存中直接获取!
注意:此时没有使用第三方缓存组件如Redis、memcached等,本地缓存内容是在内存中!
【4】@EnableCaching注解
Enables Spring’s annotation-driven cache management capability, similar to the support found in Spring’s {@code cache:*} XML namespace.
注解示例如下:
@Configuration @EnableCaching public class AppConfig { @Bean public MyService myService() { // configure and return a class having @Cacheable methods return new MyService(); } //自定义CacheManager @Bean public CacheManager cacheManager() { // configure and return an implementation of Spring's CacheManager SPI SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default"))); return cacheManager; } }
等同于Spring xml配置如下:
<beans> <cache:annotation-driven/> <bean id="myService" class="com.foo.MyService"/> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"> <property name="name" value="default"/> </bean> </set> </property> </bean> </beans>
源码说明如下:
* In both of the scenarios above, {@code @EnableCaching} and {@code * <cache:annotation-driven/>} are responsible for registering the necessary Spring * components that power annotation-driven cache management, such as the * {@link org.springframework.cache.interceptor.CacheInterceptor CacheInterceptor} and the * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when * {@link org.springframework.cache.annotation.Cacheable @Cacheable} methods are invoked. * * <p>If the JSR-107 API and Spring's JCache implementation are present, the necessary * components to manage standard cache annotations are also registered. This creates the * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when * methods annotated with {@code CacheResult}, {@code CachePut}, {@code CacheRemove} or * {@code CacheRemoveAll} are invoked. * * <p><strong>A bean of type {@link org.springframework.cache.CacheManager CacheManager} * must be registered</strong>, as there is no reasonable default that the framework can * use as a convention. And whereas the {@code <cache:annotation-driven>} element assumes * a bean <em>named</em> "cacheManager", {@code @EnableCaching} searches for a cache * manager bean <em>by type</em>. Therefore, naming of the cache manager bean method is * not significant.
【5】@Cacheable注解
源码如下:
*//标明方法返回结果应该被缓存 * //每次目标方法被调用前,都会根据给定的方法参数检查是否方法已经被调用(进行了缓存) *//默认使用方法参数得到缓存的key,但是你可以在key属性中使用SpELl表达式或者使用自定义的keyGenerator来指定key *//如果根据key没有从缓存里拿到值,就调用方法并将返回值进行缓存 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; // value等同于cacheNames,指定方法结果应该被存放的Cache组件名字 /** * Spring Expression Language (SpEL) expression for computing the key dynamically. * <p>Default is {@code ""}, meaning all method parameters are considered as a key, * unless a custom {@link #keyGenerator} has been configured. * // 默认方法的所有参数作为key,除非配置了自定义的keyGenerator * <p>The SpEL expression evaluates against a dedicated context that provides the * following meta-data: * <ul> * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for * references to the {@link java.lang.reflect.Method method}, target object, and * affected cache(s) respectively.</li> * <li>Shortcuts for the method name ({@code #root.methodName}) and target class * ({@code #root.targetClass}) are also available. * <li>Method arguments can be accessed by index. For instance the second argument * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments * can also be accessed by name if that information is available.</li> * </ul> */ String key() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} * to use. * <p>Mutually exclusive with the {@link #key} attribute. * @see CacheConfig#keyGenerator */ String keyGenerator() default ""; /** * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none * is set already. * <p>Mutually exclusive with the {@link #cacheResolver} attribute. * @see org.springframework.cache.interceptor.SimpleCacheResolver * @see CacheConfig#cacheManager */ String cacheManager() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} * to use. * @see CacheConfig#cacheResolver */ String cacheResolver() default ""; /** * Spring Expression Language (SpEL) expression used for making the method * caching conditional. * <p>Default is {@code ""}, meaning the method result is always cached. * <p>The SpEL expression evaluates against a dedicated context that provides the * following meta-data: * <ul> * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for * references to the {@link java.lang.reflect.Method method}, target object, and * affected cache(s) respectively.</li> * <li>Shortcuts for the method name ({@code #root.methodName}) and target class * ({@code #root.targetClass}) are also available. * <li>Method arguments can be accessed by index. For instance the second argument * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments * can also be accessed by name if that information is available.</li> * </ul> */ String condition() default ""; /** * Spring Expression Language (SpEL) expression used to veto method caching. * <p>Unlike {@link #condition}, this expression is evaluated after the method * has been called and can therefore refer to the {@code result}. * <p>Default is {@code ""}, meaning that caching is never vetoed. * <p>The SpEL expression evaluates against a dedicated context that provides the * following meta-data: * <ul> * <li>{@code #result} for a reference to the result of the method invocation. For * supported wrappers such as {@code Optional}, {@code #result} refers to the actual * object, not the wrapper</li> * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for * references to the {@link java.lang.reflect.Method method}, target object, and * affected cache(s) respectively.</li> * <li>Shortcuts for the method name ({@code #root.methodName}) and target class * ({@code #root.targetClass}) are also available. * <li>Method arguments can be accessed by index. For instance the second argument * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments * can also be accessed by name if that information is available.</li> * </ul> * @since 3.2 */ String unless() default ""; /** * Synchronize the invocation of the underlying method if several threads are * attempting to load a value for the same key. The synchronization leads to * a couple of limitations: * <ol> * <li>{@link #unless()} is not supported</li> * <li>Only one cache may be specified</li> * <li>No other cache-related operation can be combined</li> * </ol> * This is effectively a hint and the actual cache provider that you are * using may not support it in a synchronized fashion. Check your provider * documentation for more details on the actual semantics. * @since 4.3 * @see org.springframework.cache.Cache#get(Object, Callable) */ boolean sync() default false; }
CacheManager管理多个Cache组件,对缓存真正的CRUD操作在Cache组件中,每一个Cache组件有自己唯一一个名字。
@Cacheable注解属性简解如下:
cacheNames/value:指定缓存组件的名字,必须指定至少一个。
@Cacheable(value=”mycache”) @Cacheable(value={”cache1”,”cache2”}
key:缓存数据使用的key,可以根据该属性进行自定义设置,默认使用方法的参数值。
@Cacheable(value=”testcache”,key=”#userName”) @Cacheable(value=”testcache”,key=”#root.args[0]”) @Cacheable(value=”testcache”,key=”#root.methodName+'['+#id+']'”)
keyGenerator : key的生成器,可以自己指定keyGenerator 组件id(自定义keyGenerator )。
cacheManager:指定缓存管理器;cacheResolver指定获取解析器;二者二选一使用。
condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断。
@Cacheable(value=”testcache”,condition=”#id>2”) @Cacheable(value=”testcache”,condition=”#a0>2”)
unless : 当unless的条件为true时,方法的返回值就不缓存。该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存。
@Cacheable(value=”testcache”,unless=”#result == null”)
sync:是否启用异步模式。默认采用同步方式,在方法执行完将结果放入缓存,可以设置为true,启用异步模式。需要注意的是,异步模式不能与unless同时使用。
【6】Cache SpEL available metametadata
【7】注册并使用自定义keyGenerator
编写自定义的keyGenerator:
@Configuration public class MyCacheConfig { @Bean public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } }
在方法处指定keyGenerator:
@Cacheable(cacheNames = "emp",keyGenerator = "keyGenerator") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee empById = employeeMapper.getEmpById(id); return empById; }
测试如下图:
【8】@CachePut注解使用
既调用方法,又更新缓存(先调用方法,然后将方法的返回结果放进缓存),常见场景如修改了数据库的某个数据,同时更新缓存。
示例如下:
// @CachePut(value = "emp",key="#result.id") @CachePut(value = "emp",key="#employee.id") public Employee updateEmp(Employee employee){ System.out.println("调用更新方法!"); employeeMapper.updateEmp(employee); return employee; }
注意,@Cacheable中的key是不能使用#result
的 !
【9】@CacheEvict注解使用
默认先执行方法,然后根据参数作为key从缓存中删除数据。如果方法执行过程中抛出了异常,则不会删除缓存中目标数据。
示例如下:
@CacheEvict(value = "emp",key = "#id",allEntries = false) public void deleEmp(Integer id){ System.out.println("删除"+id+"号员工"); // employeeMapper.deleteEmpById(id); }
allEntries :默认false,表示是否全部删除对应缓存组件中的数据。
beforeInvocation:默认false,表示是否在方法执行执行删除缓存。
其他属性见源码如下:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheEvict { /** * Alias for {@link #cacheNames}. */ @AliasFor("cacheNames") String[] value() default {}; /** * Names of the caches to use for the cache eviction operation. * <p>Names may be used to determine the target cache (or caches), matching * the qualifier value or bean name of a specific bean definition. * @since 4.2 * @see #value * @see CacheConfig#cacheNames */ @AliasFor("value") String[] cacheNames() default {}; /** * Spring Expression Language (SpEL) expression for computing the key dynamically. * <p>Default is {@code ""}, meaning all method parameters are considered as a key, * unless a custom {@link #keyGenerator} has been set. * <p>The SpEL expression evaluates against a dedicated context that provides the * following meta-data: * <ul> * <li>{@code #result} for a reference to the result of the method invocation, which * can only be used if {@link #beforeInvocation()} is {@code false}. For supported * wrappers such as {@code Optional}, {@code #result} refers to the actual object, * not the wrapper</li> * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for * references to the {@link java.lang.reflect.Method method}, target object, and * affected cache(s) respectively.</li> * <li>Shortcuts for the method name ({@code #root.methodName}) and target class * ({@code #root.targetClass}) are also available. * <li>Method arguments can be accessed by index. For instance the second argument * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments * can also be accessed by name if that information is available.</li> * </ul> */ String key() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} * to use. * <p>Mutually exclusive with the {@link #key} attribute. * @see CacheConfig#keyGenerator */ String keyGenerator() default ""; /** * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none * is set already. * <p>Mutually exclusive with the {@link #cacheResolver} attribute. * @see org.springframework.cache.interceptor.SimpleCacheResolver * @see CacheConfig#cacheManager */ String cacheManager() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} * to use. * @see CacheConfig#cacheResolver */ String cacheResolver() default ""; /** * Spring Expression Language (SpEL) expression used for making the cache * eviction operation conditional. * <p>Default is {@code ""}, meaning the cache eviction is always performed. * <p>The SpEL expression evaluates against a dedicated context that provides the * following meta-data: * <ul> * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for * references to the {@link java.lang.reflect.Method method}, target object, and * affected cache(s) respectively.</li> * <li>Shortcuts for the method name ({@code #root.methodName}) and target class * ({@code #root.targetClass}) are also available. * <li>Method arguments can be accessed by index. For instance the second argument * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments * can also be accessed by name if that information is available.</li> * </ul> */ String condition() default ""; /** * Whether all the entries inside the cache(s) are removed. * <p>By default, only the value under the associated key is removed. * 默认只删除对应的key的数据 * <p>Note that setting this parameter to {@code true} and specifying a * {@link #key} is not allowed. * 需要注意的是#key与该属性设置为true不允许同时存在 */ boolean allEntries() default false; /** * Whether the eviction should occur before the method is invoked. * 是否在方法调用前清楚缓存 * <p>Setting this attribute to {@code true}, causes the eviction to * occur irrespective of the method outcome (i.e., whether it threw an * exception or not). * 如果设置为true,则无论方法是否正常执行,都会在方法执行前清除缓存 * <p>Defaults to {@code false}, meaning that the cache eviction operation * will occur <em>after</em> the advised method is invoked successfully (i.e., * only if the invocation did not throw an exception). * 默认在方法正常成功执行后,清除缓存 */ boolean beforeInvocation() default false; }
【10】@Caching注解
该注解用来构建复杂规则的缓存用例,源码如下:
/** * Group annotation for multiple cache annotations (of different or the same type). * * <p>This annotation may be used as a <em>meta-annotation</em> to create custom * <em>composed annotations</em> with attribute overrides. * * @author Costin Leau * @author Chris Beams * @since 3.1 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Caching { Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; }
实例如下:
@Caching( cacheable ={ @Cacheable(value ="emp",key = "#lastName") }, put = { @CachePut(value = "emp",key = "#result.id"), @CachePut(value = "emp",key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ System.out.println("调用复杂缓存方法"); return employeeMapper.getEmpByLastName(lastName); }
第一次按照lastName进行缓存的同时(cacheable 注解),@CachePut注解也起作用–分别以id和email为key在缓存中放入数据。
再次进行查询的时候方法仍然会调用,因为@CachePut注解一直起作用!
【11】@CacheConfig注解使用
同一个类中不同方法汇总缓存组件名字一般都相同,可以使用@CacheConfig注解作用在类上配置共同属性值,默认对该类的所有方法起作用。
注解源码如下:
*@CacheConfig在类级别上提供了共享与缓存相关的公共设置的机制 当类上面添加了该注解,就意味着提供了一系列默认设置关于该类上定义的缓存操作 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheConfig { /** * Names of the default caches to consider for caching operations defined * in the annotated class. * <p>If none is set at the operation level, these are used instead of the default. * <p>May be used to determine the target cache (or caches), matching the * qualifier value or the bean names of a specific bean definition. */ String[] cacheNames() default {}; * 使用在该类上面的KeyGenerator类型的bean 实例名字 * <p>If none is set at the operation level, this one is used instead of the default. * <p>The key generator is mutually exclusive with the use of a custom key. When such key is * defined for the operation, the value of this key generator is ignored. */ String keyGenerator() default ""; /** * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none * is set already. * <p>If no resolver and no cache manager are set at the operation level, and no cache * resolver is set via {@link #cacheResolver}, this one is used instead of the default. * @see org.springframework.cache.interceptor.SimpleCacheResolver */ String cacheManager() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use. * <p>If no resolver and no cache manager are set at the operation level, this one is used * instead of the default. */ String cacheResolver() default ""; }
完整示例如下:
@CacheConfig(cacheNames = "emp") @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable(key = "#id") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee empById = employeeMapper.getEmpById(id); return empById; } @CachePut(/*value = "emp",*/key="#employee.id") public Employee updateEmp(Employee employee){ System.out.println("调用更新方法!"); employeeMapper.updateEmp(employee); return employee; } @CacheEvict(/*value = "emp",*/key = "#id",allEntries = true) public void deleEmp(Integer id){ System.out.println("删除"+id+"号员工"); // employeeMapper.deleteEmpById(id); // int i=10/0; } @Caching( cacheable ={ @Cacheable(/*value ="emp",*/key = "#lastName") }, put = { @CachePut(/*value = "emp",*/key = "#result.id"), @CachePut(/*value = "emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ System.out.println("调用复杂缓存方法"); return employeeMapper.getEmpByLastName(lastName); } }
如果方法上指定了该属性值(@CacheConfig中配置的属性),则使用方法上指定的值:
//如这里put 中将#result.id为key放入emp1缓存组件中 @Caching( cacheable ={ @Cacheable(/*value ="emp",*/key = "#lastName") }, put = { @CachePut(value = "emp1",key = "#result.id"), @CachePut(/*value = "emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ System.out.println("调用复杂缓存方法"); return employeeMapper.getEmpByLastName(lastName); }
【12】整合第三方缓存-Redis
项目中引入Redis组件后,就不再使用默认的SimpleCacheConfiguration,而是使用RedisCacheConfiguration。
其源码如下:
@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @ConditionalOnBean(RedisTemplate.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class RedisCacheConfiguration { private final CacheProperties cacheProperties; private final CacheManagerCustomizers customizerInvoker; RedisCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) { this.cacheProperties = cacheProperties; this.customizerInvoker = customizerInvoker; } //向容器中注册RedisCacheManager @Bean public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { //给cacheManager 赋予redisTemplate,用来进行通信 RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return this.customizerInvoker.customize(cacheManager); } }
【13】自定义RedisCacheManager
默认使用的JDK序列化机制在redis中存的是序列化数据,非直观显示的json数据。如果想要在redis中存放为json数据,可以做如下考虑:自定义RedisCacheManager。
自定义RedisCacheManager源码示例如下:
@Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object,Employee> empRedisTemplate(RedisConnectionFactory connectionFactory){ RedisTemplate<Object,Employee> template = new RedisTemplate<Object, Employee>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class); template.setDefaultSerializer(serializer); return template; } @Bean public RedisCacheManager myCacheManager(RedisTemplate<Object,Employee> empRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate); //默认将CacheName作为key的前缀 cacheManager.setUsePrefix(true); return cacheManager; } }
这样在操作Employee时在redis中存放的即为json数据!但是这种方式有明显局限性,基本不用考虑。
继续往下看!!!
【14】自定义RedisTemplate<Object,Object>
替换容器中默认配置的RedisTemplate<Object,Object>,源码示例如下:
//替换默认注册的RedisTemplate<Object,Object>,id为redisTemplate @Bean public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){ RedisTemplate<Object,Object> template = new RedisTemplate<Object, Object>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); template.setDefaultSerializer(serializer); return template; }
这样,无需替换容器中的RedisCacheManager,即可将任意类型进行JSON化存放Redis中!
但是同样有坑,存的时候没有问题,取出来的时候就有问题了,异常如下:
所以,使用默认的序列化机制,存取对象都没问题,但是在Redis中又不友好,上面的方法显然都不行,继续往下看!!
【15】SpringBoot2.0下整合Redis
pom依赖
<!--整合Redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 1.5的版本默认采用的连接池技术是jedis 2.0以上版本默认连接池是lettuce, 在这里采用jedis,所以需要排除lettuce的jar --> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加jedis客户端 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--spring2.0集成redis所需common-pool2--> <!-- 必须加上,jedis依赖此 --> <!-- spring boot 2.0 的操作手册有标注 地址是:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> <!--<version>2.4.2</version>--> </dependency>
自定义Redis配置类
package com.hh.config; @Configuration // 必须加,使配置生效 @EnableCaching public class MyRedisConfig extends CachingConfigurerSupport { private static final Logger log = LoggerFactory.getLogger(MyRedisConfig.class); @Autowired private JedisConnectionFactory jedisConnectionFactory; @Bean @Override public CacheManager cacheManager() { // 初始化缓存管理器,在这里我们可以缓存的整体过期时间什么的,我这里默认没有配置 log.info("初始化 -> [{}]", "CacheManager RedisCacheManager Start"); RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager .RedisCacheManagerBuilder .fromConnectionFactory(jedisConnectionFactory); return builder.build(); } @Bean public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory ) { //设置序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(jedisConnectionFactory); RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); // key序列化 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value序列化 redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // Hash value序列化 redisTemplate.afterPropertiesSet(); return redisTemplate; } @Override @Bean public CacheErrorHandler errorHandler() { // 异常处理,当Redis发生异常时,打印日志,但是程序正常走 log.info("初始化 -> [{}]", "Redis CacheErrorHandler"); CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException e, Cache cache, Object key) { log.error("Redis occur handleCacheGetError:key -> [{}]", key, e); } @Override public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) { log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e); } @Override public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) { log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e); } @Override public void handleCacheClearError(RuntimeException e, Cache cache) { log.error("Redis occur handleCacheClearError:", e); } }; return cacheErrorHandler; } }
测试实例如下:
@Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate; @Test public void testRedis(){ // 测试redis if(stringRedisTemplate.hasKey("hello")){ String hello = stringRedisTemplate.opsForValue().get("hello"); System.out.println("从redis中获取 key-hello--value : "+hello); stringRedisTemplate.opsForValue().set("jane","is a boy"); } SysUser sysUser = new SysUser(); sysUser.setId(1L); sysUser.setDataType(1); sysUser.setIdentity("4125XXXX"); redisTemplate.opsForValue().set("sysUser",sysUser); SysUser sysUser2 = (SysUser) redisTemplate.opsForValue().get("sysUser"); System.out.println(sysUser2); }
测试结果如下图:
如果使用Redis作为Cache实现,那么数据将会自动被缓存到Redis中。@Cacheable
加了该注解的方法将会首先尝试从Redis中获取数据,如果没有数据则执行具体方法,将方法结果缓存到Redis中。再次查询时,将直接从Redis中获取,不会再执行具体方法。
需要注意的是,该种方式不用显示手动使用RedisTemplate进行存取!因为数据是存储在Redis中的,即使应用重启,再次查询同样直接从Redis中获取,不再执行具体方法
@Cacheable(cacheNames = "emp") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee empById = employeeMapper.getEmpById(id); return empById; }