好处:
1、加快响应速度;
2、减轻数据库压力;
3、提升服务负载能力;
缺点:
1、数据冗余存储、空间;
2、代码开发;
3、缓存服务稳定性维护;
4、存在数据一致性误差;
5、过分依赖缓存,一旦缓存失效或出现问题,数据库会出现无法预知的压力,不方便预警及实时问题修复;
image.png
Cache
说明
缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等;
具体意义
每一个cache中存储一个缓存信息,比如说:用户信息缓存(user_cache)、商品信息缓存(product_cache)...然后每一个cache中又会用具体的缓存key做具体区分,如:用户信息缓存中,根据用户id区分,那么用户id就是user_cache中的key;product_cache中又以商品id作为key区分具体缓存记录;
public interface Cache {
...
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
...
CacheManager
1、缓存管理器,管理各个缓存(Cache)组件,针对不同的业务场景,可以定义多个不同的CacheManager管理具体cache数据;
2、比如说:我们可以通过CacheManager加载各个cache,并且可以初始化各个cache的参数设置,如过期时间等;
3、我们缓存数据需要指定cache manager跟具体被其管理的cache,这样才可以正确缓存;
public interface CacheManager {
...
public abstract class AbstractTransactionSupportingCacheManager extends AbstractCacheManager {
...
image.png
@EnableCaching
开启基于注解的缓存,属于spring4.x之后的spring 缓存注解;
@CacheConfig
在类上使用@CacheConfig统一的配置缓存的信息,包括指定cacheManager、具体cache等;
@Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,具体缓存的key可以指定;
@CachePut
保证方法调用,又能将结果缓存,可以用于刷新缓存,同样key需要指定,需要跟@Cacheable中制定的key对应保持一致,同样是将方法返回结果进行保存,所以同样需要跟@Cacheable方法中的结果返回类型一致;
@CacheEvict
针对指定的key,清空缓存;
默认使用的是SimpleCacheConfiguration
image.png
pom依赖
image.png
image.png
所以:只要依赖了spring-boot-starter-web就可以使用spring默认缓存
关于配置
可以使用SpringBoot提供的默认配置,所以在使用spring默认缓存时,可以不需要要在yml文件中配置任何cache相关的信息;
但是一旦依赖了其它实现的缓存jar包如redis、ehcache,要想再使用默认,则需要指定配置。如下:
自定义cache、cache类型指定
spring:
cache:
cache-names:
- localDefaultCache
- concurrentCache
type: simple
SpringBoot启动类
开启缓存@EnableCaching
@SpringBootApplication
@MapperScan("com.fc.redis.mapper")
@EnableCaching // 开启spring缓存
public class RedisApplication {
/**
* @author GY
* @date 2019年9月13日
*/
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
缓存service类
/**
* @program: fc-redis->ConcurrentCacheTestService
* @description: 测试默认缓存manager——concurrentCache
* @author: G_Y
* @create: 2019-09-10 14:25
**/
@Service
@CacheConfig(cacheManager = "cacheManager", cacheNames = {"localDefaultCache"})
@Slf4j
public class ConcurrentCacheTestService {
@Autowired
private TbUserMapper userMapper;
@Cacheable(key = "#id") //设置缓存——方法结果
public TbUser getUser(Integer id) {
log.info("This is use ConcurrentCache cache user");
TbUser tbUser = userMapper.selectByPrimaryKey(id);
return tbUser;
}
@CachePut(key = "#user.id") // 更新缓存
public TbUser addOrUpdateUserById(TbUser user) {
user.setUpdateTime(new Date());
if (user.getId() == null || user.getId() == 0) {
userMapper.insertSelective(user);
return user;
}
userMapper.updateByPrimaryKeySelective(user);
return user;
}
// allEntries:
//是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
//beforeInvocation:
//是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
@CacheEvict(key = "#id") //清除缓存
public boolean deleteUserById(Integer id) {
int i = userMapper.deleteByPrimaryKey(id);
if (i > 0)
return true;
return false;
}
}
controller测试
因为是本地缓存,服务停止则缓存移除,所以需要长启动服务,无法使用Junit测试
@RestController
public class CacheTestController {
@Autowired
private ConcurrentCacheTestService concurrentCacheTestService;
/**
* 测试springboot默认缓存concurrentCache
*
* @param
* @return
*/
@PostMapping("/test/concurrent/cache/save")
public TbUser saveUser() {
TbUser tbUser = new TbUser();
tbUser.setName("oooo");
tbUser.setPhone("199");
tbUser.setUpdateTime(new Date());
tbUser.setCreateTime(new Date());
tbUser.setUserName("userName_oooo");
TbUser user = concurrentCacheTestService.addOrUpdateUserById(tbUser);
System.out.println(user);
return user;
}
@GetMapping("/test/concurrent/cache/get/{id}")
public TbUser getUserByIdPro(@PathVariable("id") Integer id) {
ConcurrentMapCacheManager c;
TbUser user = concurrentCacheTestService.getUser(id);
System.out.println(user);
return user;
}
}
测试结果:缓存成功
断点跟进查看CacheManager
image.png
首次访问
image.png
数据库sql日志
image.png
再次访问,则不会进入方法逻辑,直接返回缓存数据;(实现原理——动态代理(AOP),所以我猜测
如果私有方法上加上注解、或非直接调用的缓存方法,会缓存失效!)
第二次查询未访问数据库,如图:
image.png
证明缓存失效猜想
image.png
访问两次的结果日志
image.png
就算是public修饰,方法内部调用同样失效
——这跟cglib动态代理实现的原理有关,方法内部的调用,依然是原方法(super的方法逻辑),因为getUser不会存在具体的代理方法,因为他未被增强!
image.png
image.png
两次访问的结果
image.png
由此证明:猜想完全正确
关于SimpleCache——SpringBoot默认缓存选择
spring3.1之后引进了cache,我们可以使用CacheManager、Cache以及相关缓存注解将缓存集成到系统中,但spring并没有提供配置缓存超时的机制,所以,ConcurrentMapCacheManager无法直接使用超时设置,如有需要,则需要自行主动封装;
优势:
1、可持久化;——意味着可以从磁盘恢复缓存数据(可能会存在数据非同步,针对具体业务场景使用)
2、可以设置key超时过期;
缺陷
1、分布式部署,缓存不一致问题;
具体代码实现
pom依赖
net.sf.ehcache
ehcache
2.10.4
ehcache Java配置代码
@Configuration
@EnableCaching
public class CacheConfig {
// 如果想要缓存持久化跟缓存启动从磁盘恢复到内存,需要添加如下两个初始化跟终结方法;
@PostConstruct
private void init() {
// 关闭tomcat时增加删除回调的钩子
System.setProperty(net.sf.ehcache.CacheManager.ENABLE_SHUTDOWN_HOOK_PROPERTY, "true");
}
@PreDestroy
private void destroy() {
// 关闭tomcat时,执行相应的关闭
CacheManager.getInstance().shutdown();
}
// 自定义EhCacheManager到IOC容器
@Bean(value = "ehCacheCacheManager")
public EhCacheCacheManager ehCacheCacheManager() {
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
ehCacheCacheManager.setTransactionAware(true);
ehCacheCacheManager.afterPropertiesSet();
return ehCacheCacheManager;
}
}
EhCache的具体Cache配置文件
指定配置文件地址
spring:
cache:
ehcache:
config: classpath:/ehcache.xml
配置文件内容
本文使用的是其中的名为ehcache_test的具体cache做测试
缓存代码
/**
* @program: fc-redis->EhCacheTestService
* @description: ehcache
* @author: G_Y
* @create: 2019-09-10 15:07
**/
@Service
@CacheConfig(cacheManager = "ehCacheCacheManager", cacheNames = {"ehcache_test"}) //ehcache.xml 配置文件中定义
@Slf4j
public class EhCacheTestService {
@Autowired
private TbUserMapper userMapper;
@Autowired
private EhCacheCacheManager ehCacheCacheManager;
@Cacheable(key = "#id")
public TbUser getUser(Integer id) {
log.info("This is use ConcurrentCache cache user");
TbUser tbUser = userMapper.selectByPrimaryKey(id);
return tbUser;
}
@CachePut(key = "#user.id")
public TbUser addOrUpdateUserById(TbUser user) {
user.setUpdateTime(new Date());
if (user.getId() == null || user.getId() == 0) {
userMapper.insertSelective(user);
return user;
}
userMapper.updateByPrimaryKeySelective(user);
return user;
}
// allEntries:
//是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
//beforeInvocation:
//是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
@CacheEvict(key = "#id")
public boolean deleteUserById(Integer id) {
int i = userMapper.deleteByPrimaryKey(id);
if (i > 0)
return true;
return false;
}
}
调用缓存及接口
/**
* @program: fc-redis->CacheTestController
* @description: 本地缓存测试
* @author: G_Y
* @create: 2019-09-10 15:04
**/
@RestController
public class CacheTestController {
@Autowired
private EhCacheTestService ehCacheTestService;
/**
* 测试ehcache
* @param id
* @return
*/
@GetMapping("/test/ehcache/get/{id}")
public TbUser getUserById(@PathVariable("id") Integer id) {
TbUser user = ehCacheTestService.getUser(id);
System.out.println(user);
return user;
}
@PostMapping("/test/ehcache/save")
public TbUser saveUseByEhCache() {
TbUser tbUser = new TbUser();
tbUser.setName("saveUseByEhCache");
tbUser.setPhone("1999");
tbUser.setUpdateTime(new Date());
tbUser.setCreateTime(new Date());
tbUser.setUserName("userName_EhCache");
TbUser user = ehCacheTestService.addOrUpdateUserById(tbUser);
System.out.println(user);
return user;
}
}
缓存测试
image.png
第一次需要访问数据库
image.png
第二次再访问——直接用缓存数据,不在执行具体缓存方法逻辑(访问数据库查询)
image.png
关闭当前服务——数据持久化到磁盘
image.png
image.png
重启服务——重新加载持久化缓存到内存
image.png
再次访问——依旧使用缓存数据,不用访问数据库
image.png
访问新的数据——注意id
image.png
访问数据库——缓存新的数据
image.png
缓存key超时设置
具体见每一个cache的配置项,如:
image.png
至此,完成SpringBoot——EhCache的整合使用教程
优势:
1、实现缓存统一管理,实现各服务访问缓存的数据一致性;
2、分布式缓存,符合微服务设计原理;
3、易拓展、维护、高可用、负载能力强;
pom依赖
org.springframework.boot
spring-boot-starter-data-redis
Redis配置
spring:
redis:
port: 6379
database: 0
host: 127.0.0.1
password:
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
timeout: 5000ms
# redis cache中配置的名称
mycache:
cacheManagerName: redisCacheManager
userCacheName: user
productCacheName: product
Java类配置Redis对应Cache跟CacheManager
@Configuration
@EnableCaching
public class CacheConfig {
@Value("${mycache.userCacheName}")
private String USER_CACHE;
@Value("${mycache.productCacheName}")
private String PRODUCT_CACHE;
// 更改对象存储的序列化方式(默认是JDK序列话),这里采用json字符串存储对象
@Bean(value = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
Jackson2JsonRedisSerializer
缓存操作代码
@Service
@Slf4j
// 注意定义的cacheManager中可以根据cacheName 设置缓存属性 ,具体见RedisConfig
// 这里将数据缓存进入了两个cache,分别是 user、product;可以分别设置缓存属性值
@CacheConfig(cacheManager = "redisCacheManager", cacheNames = {"user", "product"}) // 实现队列 或者二级缓存
public class UserService {
//condition = "可加缓存条件" // 比如判断冷热数据 近一个月数据缓存 用户经常查询
@Cacheable(key = "#id")
public TbUser getUserById(Integer id) {
log.info("Redis not use, Query Mysql!!");
// System.out.println("Redis not use, Query Mysql!!");
return userMapper.selectByPrimaryKey(id);
}
// 更新缓存
@CachePut(key = "#user.id")
public TbUser updateUserById(TbUser user) {
user.setUpdateTime(new Date());
userMapper.updateByPrimaryKeySelective(user);
return user;
}
// allEntries:
//是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
//beforeInvocation:
//是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
@CacheEvict(key = "#id")
public boolean deleteUserById(Integer id) {
int i = userMapper.deleteByPrimaryKey(id);
if (i > 0)
return true;
return false;
}
}
测试代码
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisTest {
@Autowired
UserService userService;
@Test
public void testRedisCache() {
TbUser userById = userService.getUserById(5);
System.out.println(userById);
}
@Test
public void testRedisPut() {
TbUser user = new TbUser();
user.setId(5);
user.setPhone("177");
user.setName("bbbbbb");
user.setPhone("177");
user = userService.updateUserById(user);
System.out.println(user);
}
@Test
public void testDeleteCache() {
boolean b = userService.deleteUserById(3);
System.out.println(b);
}
测试testRedisCache结果——访问数据库
image.png
user_cache 40秒过期——再查redis 无user_cache数据
image.png
再次测试——实现走缓存,不再访问数据库,只要有一个cache中存在缓存的数据,即可
image.png
至此SpringBoot整合Redis实现spring缓存已经完成!