当程序中数据库访问压力特别大时,我们会考虑使用缓存来减少对数据库的访问次数。
在我们的项目中,springboot+Mybatis是一个比较常见的组合。Mybatis有一级缓存和二级缓存。
一级缓存:
1. 创建一个springboot项目
2. entity层和mapper层代码略
3. 在yml配置日志打印级别,打印出数据库查询语句
```
logging:
level:
top:
kooper:
demo:
log:
mapper: debug
```
其中top.kooper.demo.log.mapper包为数据库访问层
4. 编写测试代码
```
@Test
@Transactional
public void contextLoads() throws Exception {
Log log = this.logService.get(1L);
System.out.println(log.toString());
Log log3 = this.logService.get(1L);
System.out.println(log3.toString());
}
```
运行后日志打印:
```
2019-01-17 23:42:16.427 DEBUG 3689 --- [ main] t.k.d.l.m.LogMapper.selectByPrimaryKey : ==> Preparing: select id, `time`, content from log where id = ?
2019-01-17 23:42:16.473 DEBUG 3689 --- [ main] t.k.d.l.m.LogMapper.selectByPrimaryKey : ==> Parameters: 1(Long)
2019-01-17 23:42:16.554 DEBUG 3689 --- [ main] t.k.d.l.m.LogMapper.selectByPrimaryKey : <== Total: 1
Log(id=1, time=4, content=test)
Log(id=1, time=4, content=test)
```
从日志中我们可以看到程序只进行了一次数据库的查询,这是因为Mybatis默认是开启一级缓存。
一级缓存的作用域是一个SqlSession。在同一个SqlSession中,执行相同的查询SQL,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中取。
注意
-
当执行SQL时两次查询中间发生了增删改操作,则SqlSession的缓存清空。
-
当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。
二级缓存
由于一级缓存的局限性,更多的时候我们需要二级缓存,Mybatis默认是没有开启二级缓存。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。
一般使用cache+redis实现。
1. 引入pom
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
```
2. 配置文件redis
```
spring:
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
......
```
4. 在程序入口开启缓存
```
@SpringBootApplication
@EnableCaching
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
```
5. 在业务层接入缓存
```
@Service
@CacheConfig(cacheNames = {"log"})
public class LogService {
@Autowired
private LogMapper logMapper;
@Cacheable(key = "#p0")
public Log get(Long id) {
Log log = this.logMapper.selectByPrimaryKey(id);
return log;
}
@CachePut(key = "#p0.id")
public Log update(Log log) {
int update = this.logMapper.updateByPrimaryKey(log);
return log;
}
@CacheEvict(key = "#p0")
public void delete(Long id) {
this.logMapper.deleteByPrimaryKey(id);
}
}
```
注意
#p0是SpEL表达式,表示该方法的第一个参数
6. 其中注解的文档
@Cacheable
如果缓存存在,直接读取缓存值; 如果不存在调用目标方法,并将方法返回结果放入缓存
其中,注解中的属性值说明如下:
- value: 缓存名,必填。
- key:可选属性,可以使用SPEL标签自定义缓存的key。
- condition:属性指定发生的条件。
@CachePut
使用该注解标识的方法,每次都会执行目标逻辑代码,并将结果存入指定的缓存中。之后另一个方法就可以直接从相应的缓存中取出缓存数据,而不需要再去查询数据库。
- value:缓存名,必填。
- key:可选属性,可以使用SPEL标签自定义缓存的key。
@CacheEvict
标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。@CacheEvict注解属性说明如下:
- value:必填
- key:可选(默认是所有参数的组合)
- condition:缓存的条件
- allEntries:是否清空所有缓存内容,默认为 false,如果指定为 true,则方法调用后将立即清空所有缓存。
- beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。
@CacheConfig
主要用于配置该类中会用到的一些共用的缓存配置
7.缓存实现
我们可以通过配置属性spring.cache.type来强制指定缓存策略。
8. 清除缓存
-
手动通过delete方法删除
-
定时删除
@CacheEvict(allEntries = true) @Scheduled(cron = "0/8 * * * * ?") public void schedulingEvict() { }
虽然@Scheduled支持固定频率(fixRate)、固定延时(fixDelay)、cron表达式,但是带有@Scheduled注解的函数不能添加入参,所以在自动清除方面我们只能实现集合级别的清除,不能实现key级别的清除。
本文参考:
https://mrbird.cc/Spring-Boot cache.html
https://blog.csdn.net/qq_28929589/article/details/79127268