MyBatis L2 cache implementation details and precautions

Introduction to L2 cache

In the first level cache mentioned above, the largest sharing range is within a SqlSession. If multiple sqlsessions need to share cache, they need to use the second level cache. After the second level cache is enabled, the cachenexecutor will be used to decorate the Executor. Before entering the query process of the first level cache, the second level cache will be queried in the cachenexecutor. The specific workflow is as follows.

When the second level Cache is enabled, all operation statements in the same namespace affect the same Cache, that is, the second level Cache is shared by multiple sqlsessions, which is a global variable.

When cache is turned on, the process of data query execution is level 2 cache - > Level 1 cache - > database.

L2 cache configuration

To use the L2 cache correctly, you need to complete the following configuration.

First, turn on the secondary cache in MyBatis' configuration file.

<setting name="cacheEnabled" value="true"/>

Then, add the cache or cache ref tag to the Mapper XML of MyBatis.

The cache tag is used to declare that the namespace needs to use the secondary cache, and the configuration can be customized.

<cache/>   
  • Type: the type used by cache. The default is perpetual cache, which is mentioned in the first level cache.
  • eviction: define the recycling policy. The common ones are FIFO and LRU.
  • flushInterval: configure to automatically refresh the cache for a certain time, in milliseconds.
  • size: the maximum number of cached objects.
  • readOnly: read only. If read-write is configured, the corresponding entity class is required to be serializable.
  • blocking: if the corresponding key cannot be found in the cache, will it be blocked until the corresponding data enters the cache.

Cache ref refers to the cache configuration of other namespaces. The operation of two namespaces uses the same cache.

<cache-ref namespace="mapper.StudentMapper"/>

Second level cache experiment

public void testCacheWithoutCommitOrClose() throws Exception {
    SqlSession sqlSession1 = factory.openSession(false);  // Do not commit transactions automatically
    SqlSession sqlSession2 = factory.openSession(false);  // Do not commit transactions automatically

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

    System.out.println(studentMapper.getStudentById(1)); // query data base
    System.out.println(studentMapper2.getStudentById(1)); // Query database, secondary cache does not work
}
public void testCacheWithCommitOrClose() throws Exception {
    SqlSession sqlSession1 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession2 = factory.openSession(false);   // Do not commit transactions automatically

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

    System.out.println(studentMapper.getStudentById(1)); // query data base
    sqlSession1.commit(); // Commit transaction
    System.out.println(studentMapper2.getStudentById(1)); // Query cache, L2 cache works
}
public void testCacheWithUpdate() throws Exception {
    SqlSession sqlSession1 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession2 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession3 = factory.openSession(false);   // Do not commit transactions automatically

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);

    System.out.println(studentMapper.getStudentById(1)); // query data base
    sqlSession1.commit(); // Commit transaction
    System.out.println(studentMapper2.getStudentById(1)); // Query cache, L2 cache works

    studentMapper3.updateStudentName("Fang Fang",1); // Update data
    sqlSession3.commit(); // Commit transaction
    System.out.println(studentMapper2.getStudentById(1)); // Query the database, indicating that the secondary cache is cleared
}
public void testCacheWithDiffererntNamespace() throws Exception {
    SqlSession sqlSession1 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession2 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession3 = factory.openSession(false);   // Do not commit transactions automatically

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);

    System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // Associated query database
    sqlSession1.close();
    System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // Query cache, L2 cache works

    classMapper.updateClassName("Characteristic class 1",1); // Update data for one of the tables
    sqlSession3.commit(); // Commit update transaction
    System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // Still querying the cache, causing dirty reads
}
// Let ClassMapper reference StudenMapper's namespace, so that the SQL operations corresponding to the two mapping files will use the same cache
public void testCacheWithDiffererntNamespace() throws Exception {
    SqlSession sqlSession1 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession2 = factory.openSession(false);   // Do not commit transactions automatically
    SqlSession sqlSession3 = factory.openSession(false);   // Do not commit transactions automatically

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);

    System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // Associated query database
    sqlSession1.close();
    System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // Query cache, L2 cache works

    classMapper.updateClassName("Characteristic class 1",1); // Update data for one of the tables
    sqlSession3.commit(); // Commit update transaction
    System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // Associated query database, L2 cache failure
}

summary

  1. Compared with the first level Cache, the second level Cache of MyBatis realizes the sharing of Cache data between sqlsessions, and the granularity is more fine. It can reach the namespace level. Through the Cache interface, different combinations of classes are realized, and the Cache is more controllable.
  2. When MyBatis queries multiple tables, it is very likely that dirty data will appear, and there are design defects. The conditions for safe use of secondary cache are relatively strict.
  3. In the distributed environment, since the default implementation of MyBatis Cache is based on the local environment, it is inevitable that dirty data will be read in the distributed environment. It is necessary to use the centralized Cache to implement the MyBatis Cache interface, which has a certain development cost. Direct use of Redis, Memcached and other distributed caches may have lower cost and higher security.

reference resources: https://mybatis.org/mybatis-3/zh/configuration.html#settings

http://mybatis.org/spring/zh/factorybean.html

https://tech.meituan.com/2018/01/19/mybatis-cache.html

https://blog.csdn.net/u010349169/article/details/41408341

https://blog.csdn.net/mengsofts/article/details/88074790

Keywords: Java Mybatis Database xml SQL

Added by sabien on Mon, 27 Apr 2020 12:20:29 +0300