demo的下载地址:https://github.com/pshdhx/springboot-redis-cache-mysql
说明:我的mysql的版本是8.xx
1、必要的准备,数据库中的两张表,很简单,根据代码中的实体类建立即可。
application.properities
spring.datasource.url=jdbc:mysql://localhost:3306/cache?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=pshdhx
#开启驼峰写法识别
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.pshdhx.cache.mapper=debug
pom.xml:已上传github;
开始使用缓存:这里要注意的是,
@Cacheable(cacheNames = "emp"),里边的cacheNames必须要指定名字,否则cache无法被创建;
@Service
public class EmployeeService {
@Autowired
private Employeemapper employeemapper;
/**
* 将方法的结果进行缓存,以后要是在遇到相同的数据,直接从缓存中获取即可,不用调用查询数据库的方法
* @param id
* @return
*/
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeemapper.getEmployee(id);
return emp;
}
}
@MapperScan("com.pshdhx.cache.mapper")
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这里的主启动类一定要开启EnableCaching注解;
/**
* @Authtor pshdhx
* @Date 2020/12/1317:53
* @Version 1.0
*/
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id") Integer id){
Employee emp = employeeService.getEmp(id);
return emp;
}
}
直接再浏览器中访问即可。可以看到第二次访问并不会调用查询数据库中的方法;
这就是@Cacheable注解的作用
下边对@Cacheable注解的源码分析;
它有很多的属性;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
cacheName/value属性:用来指定缓存组件的名字;
key:缓存数据使用的key,它的默认是使用注解在方法中的参数,可以用SPEL表达式来指定其的值。 #id=参数id的值,#a0,@p0第一个参数的值,#root.args[0];
keyGenerator:key的生成器,可以自己自定义生成组件的id;【key和keyGenerator二选一使用】
cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器;
condition:指定符合条件的情况下才缓存;
unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断 unless = "#result == null";
sync:是否使用异步模式
源码分析(二)
CacheAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
该类下需要引入
CacheConfigurationImportSelector
/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
*/
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
我们通过debug模式打开,可以看到这里边有11个配置项;
这11个就是所有的缓存配置;-缓存配置类;
其中我们引入了pom文件中的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
我们在application.properities中加入了debug=true时,再次启动,显示;
所以,springboot默认的缓存配置是:SimpleCacheConfiguration.java
我们可以看到
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
SimpleCacheConfiguration这个配置类,给容器中加了一个Bean
ConcurrentMapCacheManager,定制化配置ConcurrentMapCacheManager
我们可以看到ConcurrentMapCacheManager.java这个类
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
实现了CacheManager, BeanClassLoaderAware这两个接口;
该类重写了这两个方法;创建了cache,并且加入到了this.cacheMap中;
@Nullable
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
而this.cacheMap是该类中的
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
就是一个Map的容器,初始化大小是16个;
根据缓存名字和缓存对象来进行判断;如果为空,则进行创建操作:
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
}
这就给容器中注册了一个CacheManager:ConcurrentMapManager
可以获取和创建ConcurrentMapCache类型的缓存组件
这时,我们就可以往缓存中放入值和取值了;
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
private final ConcurrentMap<Object, Object> store;
ConcurrentMapCache.java中有
将数据保存到了ConcurrentMap里边,取值的时候从这里边取值;
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
public void put(Object key, @Nullable Object value) {
this.store.put(key, this.toStoreValue(value));
}
整个cache的运行流程源码分析;
1、首先,页面先从缓存中取值,如果缓存中没有值,那么就调用查询数据库的方法,并且把返回的结果放入到缓存中去;
2、如果第二次取值的key相同,那么就直接从缓存中获取值;
3、如果要修改数据库中的值,只要在修改的方法中加入了注解@***,只要是key相同,那么缓存中的数据也会被修改。
@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
这是往缓存中保存数据;
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
这是从缓存中获取数据;
没有缓存,会创建一个emp的缓存,里边有个store,就是我们之前说的hashmap
使用一个key,默认就是方法的参数?
这个key就是生成出来的?策略就是方法的参数:
我们默认的就是使用的整个key生成策略;
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
spel表达式
自定义生成key策略
package com.pshdhx.cache.config;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @Authtor pshdhx
* @Date 2020/12/1320:51
* @Version 1.0
* 自定义key生成策略
*/
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
在service中使用我们自定义的key生成策略:
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
dubug显示
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#a0>1 and #root.methodName eq 'aaa'",unless = "#a0==2")
condition是满足条件的缓存下来,unless是满足条件的不进行缓存;
sync:异步模式默认为false。
@CachePut:既调用方法,又缓存数据;
修改数据库的某个数据,同时更新到缓存
此时,修改http://localhost:8080/emp?id=1&lastName=wangwu 张三为王五,但是页面的缓存没有变,这是因为缓存的key不同;
查询的key是一个id=1,修改的key是一个employee对象,所有要指定key相同,缓存才能跟随数据库修改;
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeemapper.getEmployee(id);
return emp;
}
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
employeemapper.updateEmployee(employee);
return employee;
}
此时,页面的缓存跟着数据库在动了;
@CacheEvict清除缓存
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
/**
* key是清除指定缓存,
* allEntries是清除所有缓存,allEntries = true
* @param id
*/
@CacheEvict(value = "emp",key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
}
果然,经测试后,缓存清除;
此外,还有一个属性:
beforeInvocation = false,这个属性是清除缓存是在方法执行之后清除的【默认】;如果方法执行过程中出现了错误,那么就不清空缓存;
beforeInvocation = true,这是清空缓存是方法执行之前清空,无论方法是否执行成功,都是要清除缓存的;
@Caching注解=组合
@Target({ElementType.TYPE, ElementType.METHOD})
@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")
}
)//Key选择了lastname作key,但是put中使用了result.id作为key缓存,所有传递id也能从缓存中取到数据;
public Employee getEmployeeByLastName(String lastName){
return employeemapper.getEmployee(lastName);
}
package com.pshdhx.cache.mapper;
import com.pshdhx.cache.domain.Employee;
import org.apache.ibatis.annotations.*;
/**
* @Authtor pshdhx
* @Date 2020/12/1021:13
* @Version 1.0
*/
@Mapper
public interface Employeemapper {
@Select("select * from employee where id = #{id}")
public Employee getEmployee(Integer Id);
@Update("update employee set last_name = #{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id = #{id}")
public void updateEmployee(Employee employee);
@Delete("delete from employee where id=#{id}")
public void deleteEmp(Integer id);
@Insert("insert into employee(last_name,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("select * from employee where last_name=#{lastName}")
public Employee getEmployee(String lastName);
}
整理
给每个方法写value太麻烦了,我们能在类中写;
至此,cache源码和demo分析结束;
下一篇:springboot+cache+redis整合篇;