mybatis 缓存
一级缓存
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
- MyBatis的一级缓存最大范围是SqlSession内部,在多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
- 一级缓存默认开启
- 一级缓存是session级别的
- sqlsession执行dml (insert/update/delete)、close、clearCache等方法,会释放localcache中的对象(引用),一级缓存不可用
- mytabis一级缓存在表被删除更新操作时缓存对象引用会被移除
- 一级缓存是会话级别的
- mybatis-plus selectList和updateBatchBy方法使用了两个不同的sqlSession.
二级缓存
在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
第三方缓存redis
mybaits-plus 整合springboot2.3.1.RELEASE步骤
- 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置yml属性文件(包括redis连接等信息)
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
cache-enabled: true # 启用缓存
call-setters-on-nulls: true
jdbc-type-for-null: 'null'
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
......
redis:
database: 8
host: 0.0.0.1
port: 6379
password: passwd # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
- 编写缓存配置文件 MybatisRedisCache.java
package x.xx.modules.cache;
import cn.hutool.core.collection.CollUtil;
import io.renren.modules.job.multi.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.ContextLoader;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/***
* @description
* @author z
* @date: 2021/7/19
* @version: v1.0
*/
@Slf4j
public class MybatisRedisCache implements Cache
{
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
//这里使用了redis缓存,使用springboot自动注入
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private String id;
public MybatisRedisCache(final String id)
{
if (id == null)
{
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId()
{
return this.id;
}
@Override
public void putObject(Object key, Object value)
{
if (redisTemplate == null)
{
//由于启动期间注入失败,只能运行期间注入,这段代码可以删除
redisTemplate = SpringUtils.getBean("redisTemplate");
}
if (value != null)
{
redisTemplate.opsForValue().set(key.toString(), value);
}
log.info("缓存key{},value{}",key,value);
}
@Override
public Object getObject(Object key)
{
try
{
if (key != null)
{
log.info("获取缓存key{}",key);
return redisTemplate.opsForValue().get(key.toString());
}
} catch (Exception e)
{
log.error("缓存出错 ");
}
return null;
}
@Override
public Object removeObject(Object key)
{
if (key != null)
{
log.info("移除缓存key{}",key);
redisTemplate.delete(key.toString());
}
return null;
}
@Override
public void clear()
{
log.debug("清空缓存");
if (redisTemplate == null)
{
redisTemplate = SpringUtils.getBean("redisTemplate");
}
Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
if (!CollUtil.isEmpty(keys))
{
redisTemplate.delete(keys);
}
}
@Override
public int getSize()
{
Long size = redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock()
{
return this.readWriteLock;
}
}
- mapper文件使用如上自定义缓存
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:命名空间,用于隔离sql -->
<!-- 还有一个很重要的作用,使用动态代理开发DAO. namespace必须和Mapper接口类路径一致 -->
<mapper namespace="com.vince.dao.UserDao">
<cache type=""x.xx.modules.cache.MybatisRedisCache""></cache> // 使用自定义缓存组件
<!--查寻所有-->
<select id="selectAll" resultType="User">
SELECT * FROM t_user
</select>
</mapper>
- 执行测试
2021-07-19 17:10:51.681 [http-nio-4006-exec-11] INFO io.renren.modules.cache.MybatisRedisCache.getObject:74 - 获取缓存key-2088829177:4896908397:io.renren.modules.homestead.dao.BusRegionDao.getRegions:0:2147483647:SELECT `id`,`region_id`,`region_seq`,`reg_name`,`has_child_area`,`has_child_group`,`create_date` FROM tb_bus_region
WHERE LENGTH(region_id) = 4 AND region_id LIKE CONCAT(?, '%') AND region_id != ?
order by region_id:13:13:MybatisSqlSessionFactoryBean
Cache Hit Ratio [io.renren.modules.homestead.dao.BusRegionDao]: 0.9
源码分析
org.apache.ibatis.executor.BaseExecutor
查询方法
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //获取缓存
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}