查询缓存主要是为了提高查询访问速度,即当用户执行一次查询后,会将该数据结果放到缓存中,当下次再执行此查询时就不会访问数据库了而是直接从缓存中获取该数据。 如果在缓存中找到了数据那叫做命中。
同一sqlSession 多次查询同一SQL时会使用缓存
@Test
public void testLocalCache() throws Exception {
SqlSession sqlSession = factory.openSession(); // 自动提交事务
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
// 第二三次会从缓存中拿数据,不查数据库
System.out.println(studentMapper.getStudentById(1));
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
}
复制代码
同一sqlSession 有增删改时会清空缓存
@Test
public void testLocalCacheClear() throws Exception {
SqlSession sqlSession = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
// 增删改会清空缓存
System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "个学生");
// 会从数据库查数据
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
}
复制代码
对SqlSession的操作mybatis内部都是通过Executor来执行的。Executor的生命周期和SqlSession是一致的。Mybatis在Executor中创建了一级缓存,基于PerpetualCache 类的 HashMap
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
// 执行器
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List> cursorList;
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue deferredLoads;
// 缓存实例
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.configuration = configuration; this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue();
this.closed = false; this.wrapperExecutor = this;
//mybatis一级缓存,在创建SqlSession->Executor时候动态创建,随着sqlSession销毁而销毁
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
}
}
// 缓存实现类
public class PerpetualCache implements Cache {
private String id;
private Map
//SqlSession.selectList会调用此方法(一级缓存操作,总是先查询一级缓存,缓存中不存在再查询数据库)
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
//如果已经关闭,报错
throw new ExecutorException("Executor was closed.");
}
//先清一级缓存,再查询,但仅仅查询堆栈为0才清,为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
//加一,这样递归调用到上面的时候就不会再清局部缓存了
queryStack++;
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
//如果查到localCache缓存,处理
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库查
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
finally {
queryStack--; //清空堆栈
}
if (queryStack == 0) {
//延迟加载队列中所有元素
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); //清空延迟加载队列
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
复制代码
localCache 缓存的key 为CacheKey对象 CacheKey:statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
复制代码
如果想要设置增删改操作的时候不清空二级缓存的话,可以在其insert或delete或update中添加属性flushCache=”false”,默认为 true。
DELETE FROM t_student where id=#{id}
复制代码
// mybatis-config.xml 中配置
默认值为 true。即二级缓存默认是开启的
// 具体mapper.xml 中配置
复制代码
@Test
public void testCacheWithCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
//sqlSession1关闭后,会将sqlsession1中的数据写到二级缓存区域
//不关闭的话不会写入二级缓存
sqlSession1.close();
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}
复制代码
@Test
public void testCacheWithoutCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
//sqlSession未关闭,不会将数据写到二级缓存区域,会从数据库中查询
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}
复制代码
复制代码
复制代码
在一个命名空间下使用二级缓存 二级缓存对于不同的命名空间namespace的数据是互不干扰的,倘若多个namespace中对一个表进行操作的话,就会导致这不同的namespace中的数据不一致的情况。
在单表上使用二级缓存 在做关联关系查询时,就会发生多表的操作,此时有可能这些表存在于多个namespace中,这就会出现上一条内容出现的问题了。
查询多于修改时使用二级缓存 在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
作者:进阶的小码农
链接:https://juejin.im/post/5b97bedff265da0a89301667
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。