Кэш Mybatis под SpringBoot

MyBatis
Кэш Mybatis под SpringBoot

задний план

   Говоря о mybatis, как Java-программист, каждый должен знать, что это широко используемая среда доступа к базам данных. Вместе со Spring и Struts они образуют трех мушкетеров веб-разработки на Java — SSM. Конечно, с развитием Spring Boot все больше и больше компаний используют модель Spring Boot + mybatis, и наша компания не исключение. А mybatis используется только у меня.Я не думал о том, как его понять, не говоря уже о его механизме кеширования, до того незабвенного БАГА. Предыстория истории относительно длинная, но не многословная, она просто позволяет читателям узнать сцену, вызванную этой ошибкой, и углубить их память. При возникновении подобных проблем его можно быстро локализовать.

Давайте сначала поговорим о предпосылке истории.Чтобы пользователи не могли вводить специальные символы в динамике, динамика пользователя кодируется и отправляется на задний план, а фон будет декодироваться перед сохранением в таблице БД для облегчения просмотра. в БД и отчётность в поисковик. . При запросе пользовательской динамики сначала читайте из таблицы БД и выполняйте кодирование в фоновом режиме, прежде чем отправлять его во внешний интерфейс, а затем внешний интерфейс может декодировать его и отображать в обычном режиме. Процесс выглядит следующим образом:

Однажды после того, как была выпущена задняя среда предварительной продукции, некоторые из динамических страниц пользователя были динамически отображены нормально, в то время как другие были закодированы. Первая реакция после просмотра явления заключается в том, что динамический рассматриваемый кодируется дважды, но операция кодирования доступна только в FindbyID сервисного слоя. Теория не делает такие ошибки низкоуровневых на вершине. Без дальнейшего ADO я начал исследовать вновь добавленный код и обнаружил, что до тех пор, пока он ввел в ветку, если в недавно добавленном коде он был закодирован дважды. За исключением звонка FindbyID снова (не требуется не обсуждается), нет другого специального кода в ветке. После озадачивания я консультировался со старым водителем рядом со мной. Старый водитель сказал, что это может быть кеш MyBatis. Поэтому я посмотрел на мой код, удалил операцию кодирования из FindbyID и выпустил ее в предварительный выпуск снова. Это было нормально. Я думал, что старый водитель достойна быть старым водителем. Существует два условия, которые необходимо обратить внимание, когда эта ошибка срабатывает:

  • Весь рабочий процесс находится в функции, и функция аннотируется @Transactional (в том же СЕССИИ для mybatis)
  • Как правило, findByIdy будет вызываться только один раз, если он входит в ветку, то будет вызываться дважды (первый вызов кодируется и затем кешируется, а второй раз считывается из кеша и продолжает кодироваться)

  Я начал гуглить механизм кэширования mybatis и нашел очень хорошую статью "Расскажите о механизме кэширования mybatis", всем рекомендую к просмотру. Но в этой статье говорится об исходном коде, который глубже. А про точки знаний кеша под mybatis под SpringBoot я не рассказывал, поэтому сделал эту статью как дополнение.

конфигурация кэша

Среда SpringBoot + mybatis для создания очень простого и множества онлайн-учебников, не вторгайтесь сюда, помните, что вы можете загрузить исходный код в проекте mytatis. mybaits всего два кеша: ключ конфигурации кеша — localCacheScope, а ключ конфигурации вторичного кеша — cacheEnabled, из имени можно получить следующую информацию:

  • Кэш первого уровня — это локальный или локальный кеш, его нельзя закрыть, можно настроить только диапазон кеша. СЕССИЯ или ЗАЯВЛЕНИЕ.

  • Кэш второго уровня — это ортодоксия mybatis, и его функция будет более мощной.

   Давайте сначала рассмотрим соответствующую информацию о том, как настроить кеш mybatis в SpringBoot. По умолчанию кеш первого уровня mybatis под SpringBoot имеет уровень SESSION, а кеш второго уровня тоже открыт, что можно увидеть в файле org.apache.ibatis.session.Configuration.class в исходном коде mybatis ( открытый в идее), как показано ниже:

  Вы также можете проверить статус открытия кеша с помощью следующей тестовой программы:

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnApplicationTests {
    private SqlSessionFactory factory;
    @Before
    public void setUp() throws Exception {

        InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
        factory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void showDefaultCacheConfiguration() {
        System.out.println("一级缓存范围: " + factory.getConfiguration().getLocalCacheScope());
        System.out.println("二级缓存是否被启用: " + factory.getConfiguration().isCacheEnabled());
    }
}

   Если вы хотите установить уровень кеша первого уровня и переключить кеш второго уровня, добавьте следующую конфигурацию в mybatis-config.xml (конечно, ее можно настроить и в application.xml/yml):

<settings>
  <setting name="cacheEnabled" value="true/false"/>
  <setting name="localCacheScope" value="SESSION/STATEMENT"/>
</settings>

   Но следует отметить, что кеш второго уровня cacheEnabled — это всего лишь общий переключатель, если вы хотите, чтобы кеш второго уровня действительно вступил в силу, вам нужно добавить его в xml-файл маппера. Кэш первого уровня используется только между одним и тем же SESSION или STATEMENT, а кэш второго уровня может пересекать SESSION. Если он включен, по умолчанию они имеют следующие характеристики:

  • Все выборочные заявления в файле карты будут кэшированы
  • Все операторы вставки/обновления/удаления в файле карты будут очищать кеш

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

<cache
        type = "org.mybatis.caches.ehcache.LoggingEhcache"  //指定使用的缓存类,mybatis默认使用HashMap进行缓存,可以指定第三方缓存
        eviction = "LRU"  //默认是 LRU 淘汰缓存的算法,有如下几种:
                          //1.LRU – 最近最少使用的:移除最长时间不被使用的对象。 
                          //2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。  
                          //3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。  
                          //4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
        flushInterval = "1000"  //清空缓存的时间间隔,单位毫秒,可以被设置为任意的正整数。  默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
        size = "100"      //缓存对象的个数,任意正整数,默认值是1024。
        readOnly  = "true"  //缓存是否只读,提高读取效率
        blocking = "true"   //是否使用阻塞缓存,默认为false,当指定为true时将采用BlockingCache进行封装,blocking,
                           //阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,
                           //否则会在查询数据库以后再释放锁这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。  
/>

кеш триггера

  1. Настройте кеш первого уровня на уровень SESSION.

  Контроллер дважды вызывает getOne, код выглядит следующим образом:

@RequestMapping("/getUser")
public UserEntity getUser(Long id) {
    //第一次调用
    UserEntity user1=userMapper.getOne(id);
    //第二次调用
    UserEntity user2=userMapper.getOne(id);
    return user1;
}

перечислить:http://localhost:8080/getUser?id=1, результат печати выглядит следующим образом:

   Из 1/2/3/4 на рисунке видно, что каждый раз вызов интерфейса уровня картографа, такого как getOne, будет создавать сеанс и закрывать сеанс после выполнения. Таким образом, два вызова не находятся в одном сеансе, и кеш первого уровня не работает. Чтобы начать транзакцию, код уровня контроллера выглядит следующим образом:

@RequestMapping("/getUser")
@Transactional(rollbackFor = Throwable.class)
public UserEntity getUser(Long id) {
    //第一次调用
    UserEntity user1=userMapper.getOne(id);
    //第二次调用
    UserEntity user2=userMapper.getOne(id);
    return user1;
}

   Результат печати выглядит следующим образом:

  Поскольку в одной и той же транзакции, несмотря на то, что операция select вызывается дважды, а sql выполняется только один раз, кеш играет роль. Это тот же сценарий BUG, ​​с которым я столкнулся в начале: тот же сеанс и вызов select > 1 раз. Если операция обновления вставлена ​​в середине двух вызовов, кеш будет немедленно аннулирован. Пока в сеансе есть операторы вставки, обновления и удаления, кэш в этом сеансе немедленно сбрасывается. Но обратите внимание, что это только между одной и той же сессией. Между разными сеансами, такими как сеанс 1 и сеанс 2, вставка/обновление/удаление в сеансе 1 не повлияет на кеш в сеансе 2, который будет генерировать грязные данные в случае высокого параллелизма или распределения. Поэтому рекомендуется настроить уровень кэша первого уровня на оператор.

  1. Настройте кэш L1 к уровню оператора

Опять же (1) В транзакции и нет никаких кодов транзакций снова выполняется, результат печати всегда выглядит следующим образом:

После того, как    настроен как SATEMENT, кэш первого уровня эквивалентен закрытию. Уровень STATEMENT временно смоделировать непросто, но я предполагаю, что уровень STATEMENT кэшируется в том же интерфейсе, который выполняет sql (например, в getOne выше), и кеш будет недействительным, когда getOne отсутствует.

  1. Настройте кеш второго уровня и установите для кеша первого уровня значение STATEMENT, чтобы избежать помех кеша первого уровня.

Код для удаления аннотации @Transactional из   Controller выглядит следующим образом:

@RequestMapping("/getUser")
public UserEntity getUser(Long id) {
    UserEntity user1=userMapper.getOne(id);
    UserEntity user2=userMapper.getOne(id);
    return user1;
}

   Разумеется, переключатель кеша второго уровня гарантированно будет включен.Добавьте в xml-файл маппера.Весь код файла выглядит следующим образом:

<?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" >
<mapper namespace="com.binggle.learn.dao.mapper.UserMapper" >
<resultMap id="BaseResultMap" type="com.binggle.learn.dao.entity.UserEntity" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <result column="sex" property="sex"/>
</resultMap>
<sql id="Base_Column_List" >
        id, name, sex
</sql>
<select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
    SELECT
    <include refid="Base_Column_List" />
    FROM users
    WHERE id = #{id};
</select>
<cache />
</mapper>

воплощать в жизньhttp://localhost:8080/getUser?id=1, результат печати выглядит следующим образом:

   Из красного прямоугольника на рисунке видно, что второй запрос попадает в кеш, а 0,5 — это частота попаданий. выполнить сноваhttp://localhost:8080/getUser?id=1Результат печати следующий:

   На этот раз sql больше не выполнялся, а коэффициент попаданий в кеш вырос до 0,75, поэтому кеш второго уровня является глобальным кешем. Но диапазон его кеша тоже ограничен, а кеш первого уровня находится в том же сеансе. Хотя кэш второго уровня может пересекать сеансы, он может находиться только в одном и том же пространстве имен.Так называемое пространство имен — это xml-файл сопоставления. Конкретные эксперименты см. в разделе «Эксперименты 4 и 5 с кэшем L2» в разделе «Разговор о механизме кэширования mybatis». Давайте посмотрим на влияние конфигурации кеша второго уровня на кеш второго уровня.Чтобы наглядно увидеть эффект, изменена только следующая конфигурация:

<cache
        size="1"             //一次只能缓存一个对象
        flushInterval="5000" //刷新时间为 5s
/>

  код контроллера:

@RequestMapping("/getUser")
public UserEntity getUser(Long id, Long id2) {
    //第一个对象 1
    System.out.println("================缓存对象 1=================");
    UserEntity user1 = userMapper.getOne(id);
    //另一个对象 2
    System.out.println("========缓存对象 2,剔除缓存中的对象 1=======");
    UserEntity user2=userMapper.getOne(id2);
    user2 = userMapper.getOne(id2);

    //再次读取第一个对象
    System.out.println("==========缓存被剔除,执行查询 sql===========");
    user1 = userMapper.getOne(id);

    //暂停 5s
    try {
        sleep(5000);
    }catch (Exception e){
        e.printStackTrace();
    }

    System.out.println("============5s 后再次查询对象 2=============");
    user2 = userMapper.getOne(id2);

    return user1;
}

воплощать в жизньhttp://localhost:8080/getUser?id=1&id2=2Окончательный результат печати выглядит следующим образом:

   слишком длинный, соединение:
Видно, что только вторичный кеш и объекты кеша через 5 с после сбоя вступают в силу. Существует также важный тип конфигурации кеша, конфигурация может быть сторонним кешем, особенно в условиях высокой параллелизма и распределения. Конечно, использование более специализированного распределенного кеша является ключевым, например, redis и т.д.

Суммировать

   Изначально хотел что-то резюмировать, но мне показалось, что резюме в рекомендованной статье было очень хорошим, и я прямо процитировал:

  1. Жизненный цикл кэша первого уровня MyBatis такой же, как у SqlSession.
  2. Внутренняя структура кеша первого уровня MyBatis проста, это просто HashMap без ограничения емкости, а функциональность кеша отсутствует.
  3. Максимальная область кэша первого уровня MyBatis находится внутри SqlSession. В нескольких сеансах SqlSession или в распределенной среде операция записи в базу данных вызовет грязные данные. Рекомендуется установить уровень кэша на оператор.
  4. По сравнению с кешем первого уровня, кеш второго уровня MyBatis реализует совместное использование кэшированных данных между SqlSessions, и в то же время степень детализации является более тонкой. 5. Когда MyBatis запрашивает несколько таблиц, очень вероятно, что появятся грязные данные, есть недочеты в дизайне, а условия для безопасного использования кеша второго уровня жесткие.
  5. В распределенной среде, поскольку реализация MyBatis Cache по умолчанию основана на локальной среде, грязные данные неизбежно будут считаны в распределенной среде.Для реализации интерфейса MyBatis Cache необходимо использовать централизованный кеш, что требует определенных затрат на разработку. распределенные кеши, такие как Redis, Memcached и т. д., могут быть менее дорогими и более безопасными.
  6. Лично рекомендуется отключить функцию кэширования MyBatis в производственной среде, и может быть более подходящим использовать ее в качестве платформы ORM.

Ссылаться на

Расскажите о механизме кэширования MyBatis

   Не забудьте обратить внимание на официальный аккаунт, в котором записан путь обучения программиста C++ Java.