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
- 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.
- 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.
- 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