个人博客 在很多应用场景中通常是获取前后相同或更新不频繁的数据,比如访问产品信息数据、网页数据。如果没有使用缓存,则访问每次需要重复请求数据库,这会导致大部分时间都耗费在数据库查询和方法调用上,因为数据库进行I/O操作非常耗费时间,这时就可以利用Spring Cache来解决。
Spring Cache 是Spring提供的一整套缓存解决方案。它本身并不提供缓存实现,而是提供统一的接口和代码规范、配置、注解等,以便整合各种Cache方案,使用户不用关心Cache的细节。
Spring Cache作用在方法上。当调用一个缓存方法时,会把方法参数和返回结果作为一个“键值对”(key/value)存放在缓存中,下次用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache时,要保证在缓存的方法和方法参数相同时返回相同的结果。
1、声明式注解 注解 说明 @EnableCaching 启动类声明,用来开启缓存 @Cacheable 可以作用在类和方法上,以键值对的方式缓存类或方法的返回值 @CachePut 方法被调用,然后结果被缓存 @CacheEvict 清空缓存 @Caching 用来组合多个注解标签
@Cacheable
会先查询是否已有缓存,没有则再执行方法,将返回值缓存起来,key可以有默认策略和自定义策略。用于查询热点数据。@CachePut
每次都会执行方法,并将方法的返回值缓存。用于更新数据。2、Maven依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.1.4.RELEASE</version > </parent > <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.10</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 1.3.2</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > </dependencies >
3、应用缓存 3.1、Redis配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager (RedisConnectionFactory redisConnectionFactory) { Map<String, RedisCacheConfiguration> cacheConfigMap = new HashMap <>(); cacheConfigMap.put("users" , this .getCacheConfigurationWithTtl(2 * 60 * 60 )); return RedisCacheManager.builder(redisConnectionFactory) .withInitialCacheConfigurations(cacheConfigMap) .cacheDefaults(this .getCacheConfigurationWithTtl(1 * 60 * 60 )) .build(); } private RedisCacheConfiguration getCacheConfigurationWithTtl (long seconds) { Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer (Object.class); ObjectMapper om = new ObjectMapper (); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration .defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer ())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial)) .disableCachingNullValues() .entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; } }
这里主要配置了序列化方式和对于不同缓存的过期时长设置。
3.2、数据层 建表语句
1 2 3 4 5 6 7 8 9 10 11 DROP TABLE IF EXISTS user ;CREATE TABLE user ( id bigint (20 ) NOT NULL AUTO_INCREMENT, name varchar (64 ) COMMENT '姓名' , age int (4 ) COMMENT '年龄' , PRIMARY KEY (id) )COMMENT = '用户表' ; insert into user values (1 , '赵晓斌' , 28 );insert into user values (2 , '李白' , 22 );insert into user values (3 , '宋老三' , 30 );
实体类,一定要实现Serializable
接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Data public class User implements Serializable { private static final long serialVersionUID = 5485617646232613710L ; private Long id; private String name; private int age; @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; User user = (User) o; return age == user.age && Objects.equals(id, user.id) && Objects.equals(name, user.name); } @Override public int hashCode () { return Objects.hash(id, name, age); } }
缓存层接口
1 2 3 4 5 public interface UserCacheService { User findById (Long id) ; User updateUserById (User user) ; void deleteById (Long id) ; }
这里采用Mybatis来操作数据库,具体操作和配置不在这里展开。
3.3、缓存查询数据 1 2 3 4 5 6 7 8 9 10 11 12 @Service @CacheConfig(cacheNames = "users") public class UserCacheServiceImpl implements UserCacheService { @Autowired private UserDao userDao; @Override @Cacheable(key = "#p0") public User findById (Long id) { return userDao.findById(id); } }
根据key值查询缓存,如果没有则执行方法查询数据库,将结果缓存起来,下次同样的key查询时直接从缓存取值返回,不再执行方法。
@CacheConfig
注解指定缓存key的前缀为users
。@Cacheable
等注解指定key时,使用SpEL表达式:#p0
:表示取第一个参数。#p0.id
:表示取第一个参数的id属性。#id
:表示取参数id。3.4、更新缓存数据 1 2 3 4 5 6 7 @Override @CachePut(key = "#p0.id") @Transactional public User updateUserById (User user) { userDao.updateUserById(user); return user; }
每次更新完数据库后,同步更新缓存中的数据。需要保持和缓存时的key相同。
3.5、删除缓存数据 1 2 3 4 5 6 @Override @CacheEvict(key = "#id") @Transactional public void deleteById (Long id) { userDao.deleteById(id); }
在删除完数据库的数据后,同步删除缓存中的数据。也是需要保持和缓存时的key相同。 如果需要批量删除某一类key,只需要把@CacheEvict
注解中的allEntries
属性设为true
,那就会清空所有缓存数据(仅限于同一个cacheNames
也就是@CacheConfig
注解指定的key前缀)。
4、测试验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @RestController public class CacheController { @Autowired private UserCacheService userCacheService; @RequestMapping("/findById") public User findById (Long id) { return userCacheService.findById(id); } @RequestMapping("/updateUserById") public void updateUserById (User user) { userCacheService.updateUserById(user); } @RequestMapping("/deleteById") public void deleteById (Long id) { userCacheService.deleteById(id); } }
在第一次查询时,可以看到有查询数据库的日志输出。
1 2 3 4 5 6 7 8 9 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3] was not registered for synchronization because synchronization is not active JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4945f51a] will not be managed by Spring ==> Preparing: select id, name, age from user where id = ? ==> Parameters: 3(Long) <== Columns: id, name, age <== Row: 3, ares, 33 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3]
查询Redis,可以看到已经有数据缓存了,过期时间就是前面配置中设置的2小时。
1 2 3 4 5 6 127.0.0.1:9427> keys * users::3 127.0.0.1:9427> get users::3 ["net.zhaoxiaobin.cache.domain.User",{"id":3,"name":"ares","age":22}] 127.0.0.1:9427> ttl users::3 7185
再次查询,发现依旧可以得到相同结果,但没有查询数据库的日志输出,说明缓存生效了。
修改字段更新数据库,然后再查询,可以看到缓存数据也被更新。
删除一条数据后,再根据id查询就得到空,说明缓存中的这条数据也已被删除了。
参考链接 代码地址