mybatis Details (9) - - Level 1 Cache and Level 2 Cache

Keywords: Java SQL Mybatis Database Session

In the last chapter, we explained how to improve query efficiency by lazy loading of mybatis. What other methods can improve query efficiency besides lazy loading? This is the cache we talked about in this chapter.

mybatis provides us with a first-level cache and a second-level cache, which can be understood by the following figure:

  

First level cache is SqlSession level cache. The sqlSession object needs to be constructed when operating the database. There is a data structure (HashMap) in the object to store the cached data. The cache data area (HashMap) between different sqlSessions does not affect each other.

Secondary cache is mapper-level cache. Multiple SqlSessions operate on the same Mapper's sql statement. Multiple SqlSessions can share the second-level cache, and the second-level cache is cross-SqlSession.

 

1. Level 1 Cache

(1) In an sqlSession, we query the User table twice according to id to see how they issue sql statements.

@Test
public void testSelectOrderAndUserByOrderId(){
	//Generate session based on sqlSession Factory
	SqlSession sqlSession = sessionFactory.openSession();
	String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	//For the first query, sql statements are issued and the results of the query are cached
	User u1 = userMapper.selectUserByUserId(1);
	System.out.println(u1);
	
	//The second query, because it is the same sqlSession, looks for the query results in the cache
	//If so, take it out of the cache directly without interacting with the database
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}

View console printing:

 

(2) The user table is also queried twice, but an update operation is performed between the two queries.

@Test
public void testSelectOrderAndUserByOrderId(){
	//Generate session based on sqlSession Factory
	SqlSession sqlSession = sessionFactory.openSession();
	String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	//For the first query, sql statements are issued and the results of the query are cached
	User u1 = userMapper.selectUserByUserId(1);
	System.out.println(u1);
	
	//The second step is an update operation, sqlSession.commit()
	u1.setSex("female");
	userMapper.updateUserByUserId(u1);
	sqlSession.commit();
	
	//The second query, because it's the same sqlSession.commit(), empties the cache information
	//The query will also issue sql statements
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}

Console printing:

  

(3) Summary

1. Initiate the first query for user information with user id 1, first find if there is user information with id 1 in the cache, and if not, query user information from the database. User information is obtained and stored in a first-level cache.  

2. If the intermediate sqlSession performs the commit operation (insert, update, delete), the primary cache in SqlSession will be emptied. The purpose of this is to keep the latest information stored in the cache and avoid dirty reading.

3. The second time to initiate a query for user information with user id 1, first to find if there is user information with id 1 in the cache, and then to obtain user information directly from the cache.

   

 

 

 

 

2. Level 2 Cache

The principle of second-level caching is the same as that of first-level caching. The first query will put the data into the cache, and then the second query will go directly to the cache. However, the first level cache is based on sqlSession, while the second level cache is based on the namespace of mapper files. That is to say, multiple sqlSessions can share a second level cache area in a mapper. If the namespace of two mappers is the same, even two mappers, the data queried by sql in the two mappers will also have the same second level cache area.

   

How is the secondary cache used?

(1) Open secondary cache

Unlike the default opening of the first level cache, the second level cache requires us to open it manually.

First, add the following code to the global configuration file mybatis-configuration.xml:

<! - Open the secondary cache - >.
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>

Next, open the cache in the UserMapper.xml file

<! - Open the secondary cache - >.
<cache></cache>

We can see that there is an empty label < cache/> in the mapper.xml file. In fact, you can configure < cache type= "org.apache.ibatis.cache.impl.PerpetualCache"/> here. PerpetualCache is the default caching class of mybatis. We can use mybatis default cache without writing type, or we can implement Cache interface to customize cache.

  

  

We can see whether the underlying level of the secondary cache is the HashMap architecture.

 

 

(2) Serializable Serialization Interface Implemented by po Class

   

 

After the second level cache is opened, the serializable interface of the pojo that will be cached is also needed. In order to extract the cached data and perform the deserialization operation, the second level cached data storage media are various, not only in memory, but also in hard disk. If we want to retrieve the cache, we need to deserialize it. So the pojo in mybatis implements the Serializable interface.

   

(3) Testing

1. Test Level 2 Cache is not related to sqlSession

@Test
public void testTwoCache(){
	//Generate session based on sqlSession Factory
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	
	String statement = "com.ys.twocache.UserMapper.selectUserByUserId";
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
	//For the first query, sql statements are issued and the results of the query are cached
	User u1 = userMapper1.selectUserByUserId(1);
	System.out.println(u1);
	sqlSession1.close();//Close sqlSession after the first query
	
	//The second query does not issue sql statements even though sqlSession 1 has been closed
	User u2 = userMapper2.selectUserByUserId(1);
	System.out.println(u2);
	sqlSession2.close();
}

You can see the two different sqlSession s above. The first one is closed and the second query still does not issue sql queries.

 

2. The test executes commit() operation and the secondary cache data is emptied

@Test
public void testTwoCache(){
	//Generate session based on sqlSession Factory
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	SqlSession sqlSession3 = sessionFactory.openSession();
	
	String statement = "com.ys.twocache.UserMapper.selectUserByUserId";
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
	UserMapper userMapper3 = sqlSession2.getMapper(UserMapper.class);
	//For the first query, sql statements are issued and the results of the query are cached
	User u1 = userMapper1.selectUserByUserId(1);
	System.out.println(u1);
	sqlSession1.close();//Close sqlSession after the first query
	
	//Perform the update operation, commit()
	u1.setUsername("aaa");
	userMapper3.updateUserByUserId(u1);
	sqlSession3.commit();
	
	//The second query, because of the last update operation, the cached data has been cleared (to prevent dirty reading of data), here sql statements must be issued again
	User u2 = userMapper2.selectUserByUserId(1);
	System.out.println(u2);
	sqlSession2.close();
}

Check the console:

 

4. useCache and flushCache

userCache and flushCache can also be configured in mybatis. userCache is used to set whether secondary caching is disabled or not. Setting useCache=false in state can disable the secondary caching of current select statement. That is to say, every query will issue sql to query. The default is true, that is, the sql uses secondary caching.

<select id="selectUserByUserId" useCache="false" resultType="com.ys.twocache.User" parameterType="int">
	select * from user where id=#{id}
</select>

In this case, we need the latest data sql for each query, set it to useCache=false, disable secondary caching, and get it directly from the database.  

In the same namespace of mapper, if there are other insert s, update s, delete s that need to refresh the cache after manipulating the data, dirty reading will occur if the refresh cache is not executed.

Set the flushCache="true" property in the state configuration, which by default is true, i.e. refresh the cache, but not if changed to false. When using caching, dirty reading occurs if query data in database tables are manually modified.

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.ys.twocache.User" parameterType="int">
	select * from user where id=#{id}
</select>

In general, it is necessary to refresh the cache after commit operation, flushCache=true means refresh the cache, which can avoid dirty reading of the database. So we don't need to set it up, just by default.

Posted by steve55 on Tue, 04 Jun 2019 15:04:04 -0700