Тайник Mybatis (3)

задняя часть база данных SQL

тайник

Кэш запросов в основном предназначен для повышения скорости доступа к запросу, то есть, когда пользователь выполняет запрос, результат данных будет помещен в кеш, а при следующем выполнении запроса к базе данных не будет осуществляться доступ, но данные будут получены непосредственно из кеша. Если данные найдены в кеше, это называется попаданием.

Кэш 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 естьВключено по умолчаниюиз.

Если вы хотите установить операции добавления, удаления и модификации без очистки вторичного кеша, вы можете добавить атрибут flushCache="false" к его вставке, удалению или обновлению, а значение по умолчанию равно true.

<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 на одной таблице При выполнении запроса ассоциации будет выполняться операция с несколькими таблицами.В это время возможно, что эти таблицы существуют в нескольких пространствах имен, что вызовет проблему предыдущего содержимого.

  • Используйте кеш второго уровня, когда запросов больше, чем модификаций Кэш второго уровня можно использовать, когда операций запроса гораздо больше, чем операций добавления, удаления и модификации. Поскольку любые добавления, удаления и модификации будут очищать кеш второго уровня, частая очистка кеша второго уровня снизит производительность системы.