springboot使用缓存的源码分析和demo代码上传

岁酱吖の 提交于 2020-12-15 18:08:25

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整合篇;

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!