тайник
Кэш запросов в основном предназначен для повышения скорости доступа к запросу, то есть, когда пользователь выполняет запрос, результат данных будет помещен в кеш, а при следующем выполнении запроса к базе данных не будет осуществляться доступ, но данные будут получены непосредственно из кеша. Если данные найдены в кеше, это называется попаданием.
Кэш L1
- Кэш запросов первого уровня MyBatis (также называемый локальным кешем) — это локальный кеш HashMap, основанный на классе org.apache.ibatis.cache.impl.PerpetualCache.Область действия — SqlSession.
- Дважды выполнить один и тот же оператор SQL-запроса в одном и том же SqlSession. После завершения первого выполнения результат запроса будет записан в кеш, а во второй раз будут напрямую получены данные из кеша, а не запрашиваться база данных. Это уменьшает доступ к базе данных. и повышает эффективность запросов.
- Когда SqlSession завершается, кэш запросов первого уровня в SqlSession не существует. Кэш запросов уровня 1 myBatis по умолчаниюоткрытое состояние,ине может быть закрыт.
- Добавления, удаления и модификацииОчистить кэш, совершать или не совершать
- Когда SqlSession закрывается и отправляется, кеш первого уровня будет очищен.
Один и тот же 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 на основе HashMap класса PerpetualCache.
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
// 执行器
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> 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<DeferredLoad>();
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<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
}
//SqlSession.selectList会调用此方法(一级缓存操作,总是先查询一级缓存,缓存中不存在再查询数据库)
public <E> List<E> 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<E> list;
try {
//加一,这样递归调用到上面的时候就不会再清局部缓存了
queryStack++;
list = resultHandler == null ? (List<E>) 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 является объект 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<ParameterMapping> 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;
}
Сводка жизненного цикла кэша L1
- Когда MyBatis открывает сеанс, он создает новый объект SqlSession, в объекте SqlSession будет новый объект Executor, а объект Executor будет содержать новый объект PerpetualCache; когда сеанс завершится, объект SqlSession и его внутренний объект Executor Также освобождается объект PerpetualCache.
- Если SqlSession вызывает метод close(), объект PerpetualCache кэша первого уровня будет освобожден, а кэш первого уровня будет недоступен;
- Если SqlSession вызывает clearCache(), данные в объекте PerpetualCache будут очищены, но объект по-прежнему можно будет использовать;
- Любая операция обновления (update(), delete(), insert()), выполненная в SqlSession, очистит данные объекта PerpetualCache, но объект можно будет продолжать использовать;
Кэш L2
- Кэш второго уровня MyBatis:диапазон картографауровень
- SqlSession закрытДанные будут записаны в область кеша L2 после
- Операции добавления, удаления и модификации, независимо от того, отправлена функция commit() или нет, очистят кэши первого и второго уровня.
- Кэш L2 естьВключено по умолчаниюиз.
<delete id="deleteStudent" flushCache="false">
DELETE FROM t_student where id=#{id}
</delete>
Включить кеш L2
// mybatis-config.xml 中配置
<settings>
<setting name="localCacheScope" value="SESSION"/>
默认值为 true。即二级缓存默认是开启的
<setting name="cacheEnabled" value="true"/>
</settings>
// 具体mapper.xml 中配置
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型-->
<cache />
<!-- 下面的一些SQL语句暂时略 -->
</mapper>
Реализация кэша второго уровня
- Создайте метод Configuration.newExecutor() исполнителя
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
//确保ExecutorType不为空(defaultExecutorType有可能为空)
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//重点在这里,如果启用二级缓存,返回Executor的Cache包装类对象
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
- CachingExecutor
Статический режим прокси. Все операции в CachingExecutor выполняются путем вызова внутреннего объекта делегата. Кэширование следует использовать только для запросов
public class CachingExecutor implements Executor {
private Executor delegate;
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//是否需要更缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
//从缓存中获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 结果保存到缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
Другой SqlSession, тот же Mapper
- Данные будут записываться во вторичную область кеша только после закрытия SqlSession.
@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));
}
- sqlSession не закрывается, данные не будут записываться в область кеша второго уровня
@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));
}
Кэш L2 отключен
- глобальное отключение
<setting name="cacheEnabled" value="false"/>
- частичное отключение Частичное отключение — это только отключениевыборКэш второго уровня запроса, поместите атрибут в тег selectдля useCache установлено значение false, то кэш второго уровня запроса на выборку будет закрыт.
<select id="selectStudentById" useCache="false" resultMap="studentMapper">
SELECT id,name,age,score,password FROM t_student where id=#{id}
</select>
Меры предосторожности при использовании
-
Использование кеша L2 в одном пространстве имен Кэш второго уровня не взаимодействует с данными разных пространств имен.Если таблица работает в нескольких пространствах имен, данные в разных пространствах имен будут несогласованными.
-
Использование кеша L2 на одной таблице При выполнении запроса ассоциации будет выполняться операция с несколькими таблицами.В это время возможно, что эти таблицы существуют в нескольких пространствах имен, что вызовет проблему предыдущего содержимого.
-
Используйте кеш второго уровня, когда запросов больше, чем модификаций Кэш второго уровня можно использовать, когда операций запроса гораздо больше, чем операций добавления, удаления и модификации. Поскольку любые добавления, удаления и модификации будут очищать кеш второго уровня, частая очистка кеша второго уровня снизит производительность системы.