一、JSR107(复杂性较高)
Java Caching定义了5个核心接口,分别是CachingProvider,ICacheManager,Cache,Entry和Expiry。
·CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
·CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
·Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
·Entry是一个存储在Cache中的key-value对。
·Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
二、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用
JCache(JSR-107)注解简化我们开发;
·Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
·Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache,ConcurrentMapCache等;
·每沟遇用需要缓存能的方法时,Spring会检查检查指定参教的指定的目标方法是否已经被调用过,如集有就直接从缓荐中获取方法调用后的结果,如集没有
调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。·使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
1、创建项目
mybaits mysql jdbc web cache
1、搭建环境mybatis(略)
2、使用默认缓存
默认缓存使用的是: ConcurrentMapCacheManager(CacheManage),可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;后面可以配置redis
为了方便查看sql日志
logging.level.com.zy.springboot10.dao=debug
2.1 开启缓存
@EnableCaching //开启缓存 @MapperScan("com.zy.springboot10.dao") @SpringBootApplication public class Springboot10Application { public static void main(String[] args) { SpringApplication.run(Springboot10Application.class, args); } }
2.2 @Cacheable
2.2.1 service
@Service public class UserService { @Autowired public TbuserMapper tbuserMapper; /** *@Cacheable:将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法; * CacheMlanager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字; * 几个属性: * cacheNames/value:指定缓存组件的名字; * key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值 * 编写SpEL;#id;参数id的值 #a0:参数第一个值 #p0 #root.args[0] * keyGenerator:key的生成器;可以自己指定key的生成器的组件id * key/keyGenerator:二选一使用 * cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器 * condition:指定符合条件的情况下才缓存; * unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存; * 可以获取到结果进行判断unless="#result==null" / unless="#result.size()==0" * sync:是否使用异步模式 * *运行流程 * 1、每次调用 selectTbUserById 方法之前,都会去查找缓存(按照cacheNames),第一次缓存是没有tbUser的,所以会创建缓存 * 2、去cache查找缓存的内容,通过key查找,key默认是方法的参数(没有参数放回SimpleKey.Empty对象) * 3、没有查到缓存就调用目标方法(selectTbUserId) * 4、将目标方法的返回结果放到缓存中 * * 核心流程: * 1)、使用CacheManager【ConcurrentMapCacheMlanager】按照名字得到cache【ConcurrentMapCache】组件 * 2)、key使用keyGenerator生成的,默认是simpleKeyGenerator */ @Cacheable(cacheNames="tbUser",cacheManage="xx") //可以指定配置的cacheManage public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; } }
2.2.2 测试
@Autowired UserService userService; @Test public void contextLoads() { Tbuser tbuser = userService.selectTbUserById(47); //service方法只会执行一次 Tbuser tbuser1 = userService.selectTbUserById(47); }
2.2.3 补充 Cacheable
2.2.3.1 keyGenerator
@Configuration public class MyCacheConfig { @Bean("Mygenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+Arrays.asList(params).toString()+"]"; } }; } }
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator") //指定key public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; }
2.2.3.2 condition
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator",condition = "#id>1") //id>1的数据缓存,多条件使用and连接 public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; }
2.2.3.4 unless
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator",unless = "#id==1") //如果为true,不缓存 public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; }
2.3 @ CachePut(清空key对应的缓存,并添加新的缓存)
2.3.1 service
@Cacheable(cacheNames="tbUser") public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; } @CachePut(cacheNames="tbUser",key = "#tbuser.id") //@CachePut的cacheNames和@Cacheable的cacheNames不一样好像没事,key的值一定需要一样,否则之前缓存的数据就不会清空 public Tbuser updateTbUser(Tbuser tbuser){ tbuserMapper.updateByPrimaryKey(tbuser); System.out.println(tbuser); return tbuser; //必须有返回值,否在返回值为null,就变成了了 {47:null} }
2.3.2 测试
@Test public void contextLoads() { Tbuser tbuser = userService.selectTbUserById(47); Tbuser tbuser1 = userService.selectTbUserById(47); tbuser1.setUsername("update4"); userService.updateTbUser(tbuser1); //清空key是47的缓存,并添加新的key是47的缓存 Tbuser tbuser2 = userService.selectTbUserById(47);//key存在,直接取缓存 }
2.4 @ CacheEvict(缓存清空,如果删除了数据,将缓存中的某条数据或者指定的缓存组件清空)
2.4.1 sevice
@Cacheable(cacheNames="tbUser") public Tbuser selectTbUserById(Integer id){ Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id); return tbuser; } @CacheEvict(cacheNames = "tbUser") //cacheNames名字和@Cacheable中的cacheNames名字需要一样,key需要一样,可以不需要key,使用allEntries=true:清空tbUser中的所有的缓存 public void deleteTbUser(Integer id){ tbuserMapper.deleteByPrimaryKey(id); }
补充:@CacheEvict可以设置方法之前执行还是方法之后执行,默认方法之后执行,如果设置之前执行,无论 deleteTbUser方法执行过程中是否报错,都会清空缓存
2.4.2 测试
Tbuser tbuser = userService.selectTbUserById(53); Tbuser tbuser1 = userService.selectTbUserById(53); userService.deleteTbUser(53); Tbuser tbuser2 = userService.selectTbUserById(53);
2.5 @Caching(定义复杂的缓存规则)
2.5.1 service
@Caching( cacheable = { @Cacheable(cacheNames = "tbUser", key = "#tbuser.id"), }, put = { @CachePut(cacheNames = "tbUser", key = "#tbuser.name"), @CachePut(cacheNames = "tbUser", key = "#tbuser.email")} ) public Tbuser selectTbUserByName(Tbuser tbuser) { return null; }
2.6 @CacheConfig(抽取缓存的公共配置)
@CacheConfig(cacheNames = "TbUser") //在类上标明,方法上就不需要写了 @Service public class UserService {}
补充
//可以指定CacheManager,比如redis @Autowired RedisCacheManager redisCacheManager; //使用缓存管理器得到缓存,进行api调用 public void test(){ //好像使用redisTemplate也是可以的 Cache tbUser = redisCacheManager.getCache("tbUser"); //相当于@Cacheable(cacheNames = "tbUser")中指定的cacheNames tbUser.put("1","xx"); }
3、使用redis缓存中间件
首先安装安装redis,linux可以使用docker安装,windows可以直接安装
其他的缓存中间件redis、memcached、ehcache;
redis:可以作为数据库、缓存和消息中间件
3.1 引入redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
application.properties
spring.redis.host=localhost
3.1 操作redis进行crud
测试
@Autowired RedisTemplate redisTemplate;//k:v 都是对象 @Autowired StringRedisTemplate stringRedisTemplate; //k:v 都是字符串 /** * redis常见五大数据类型 * String(字符串):redisTemplate.opsForValue(); * list(列表):redisTemplate.opsForList(); * Set(集合):redisTemplate.opsForSet(); * Hash(散列):redisTemplate.opsForHash(); * zSet(有序集合):redisTemplate.opsForZSet(); */ @Test public void test1(){ //字符串操作: append:如果k存在就追加 stringRedisTemplate.opsForValue().append("k","v"); String k = stringRedisTemplate.opsForValue().get("k"); System.out.println(k); //list操作 stringRedisTemplate.opsForList().leftPushAll("list","1","2"); String s = stringRedisTemplate.opsForList().leftPop("list"); }
缓存对象
配置(让对象以json缓存到redis中)
@Configuration public class MyRedisConfig { @Bean("TbUserRedisTemplate") public RedisTemplate<Object, Tbuser> RedisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<Object, Tbuser> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<Tbuser>(Tbuser.class) { }); return redisTemplate; } }
测试
@Autowired RedisTemplate TbUserRedisTemplate; @Test public void test1(){ //使用默认的redisTemplate Tbuser tbuser = userService.selectTbUserById(54); redisTemplate.opsForSet().add("user",tbuser); Object user = redisTemplate.opsForSet().pop("user"); //可以正常的取出user,但是redis中保存的序列化的数据乱码; System.out.println(user); TbUserRedisTemplate.opsForSet().add("user1",tbuser); Tbuser user1 = ((Tbuser) TbUserRedisTemplate.opsForSet().pop("user1")); //可以正常的取出user,但是redis中保存的数据是json数据格式; System.out.println(user1); }
3.2 配置redis,直接使用默认缓存的方法使用redis就可以了,数据自动缓存在redis中,默认保存的数据都是k-v都是object
3.3 原理
1、引入了redis的starter,cacheManager变为RedisCacheManager;
2、默认创建的RedisCacheManager 操作 redis的时候使用的是 RedisTemplate<Object,Object>
3、RedisTemplate<Object,Object>是默认使用jdk的序列化机制
4、自定义CacheManager
@Configuration public class MyRedisConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory factory){ RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(1)) .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();} }
自定义RedisCacheConfiguration(序列化机制未成功)
@Bean //如果有多个CacheManager,在某一个CacheManager上指定@Primary(主配置) public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager( RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个 this.getRedisCacheConfigurationMap() // 指定 key 策略 ); } private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() { Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(); redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(3000)); redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000)); return redisCacheConfigurationMap; } private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) { Jackson2JsonRedisSerializer<Object> 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); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); //是否允许value值为空 //redisCacheConfiguration.getAllowCacheNullValues(); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith( RedisSerializationContext .SerializationPair .fromSerializer(jackson2JsonRedisSerializer) ).entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; }