This is the 8th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

MyBatis cache introduction

Like most persistence frameworks, MyBatis also provides support for level 1 and level 2 caching

  1. Level 1 Cache A HashMap local Cache based on PerpetualCache, which is stored at Session scope, and when a Session is flushed or closed, all caches in that Session are emptied.
  2. Level 2 caches have the same mechanism as Level 1 caches, with PerpetualCache and HashMap storage by default, but with Mapper(Namespace) storage scope and customizable storage source, such as Ehcache.
  3. For cache data Update mechanism, after Create/Update/Delete operations are performed on a certain scope (Session level 1 / Namespaces level 2), all caches in the select area are cleared by default.

Mybatis level 1 cache

The mapping file

<mapper namespace="com.shxt.dao.UserDao">
   
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    
    <select id="load" parameterType="int" resultMap="BaseResultMapper">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
   
</mapper>
Copy the code

Queries | l1 test

	@Test
	public voidQuery _ Level-1 Cache Test (){SqlSession SqlSession =null;
		try {
			sqlSession = MyBatisUtils.getSqlSession();

			User u1 = sqlSession.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("First query :"+u1);

			User u2 = sqlSession.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("Second query :"+u2);

		} finally{ MyBatisUtils.closeSqlSession(sqlSession); }}Copy the code

Console running result description

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,1.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1First query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.] Second query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
Copy the code

It can be seen from the above results that the load method is called twice, but the database is queried only once. The reason for this phenomenon is the level 1 cache of MyBatis, which is enabled by default.

Query – change – queries | l1 test

	@Test
	public voidQuery _ Change _ Level-1 Cache Test (){SqlSession SqlSession =null;
		try {
			sqlSession = MyBatisUtils.getSqlSession();

			User u1 = sqlSession.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("First query :"+u1);

			User u2 = new User();
			u2.setUser_id(-999);
			u2.setStatus(2);
			// Change the database
			sqlSession.update(UserDao.class.getName()+".update", u2);

			User u3 = sqlSession.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("Second query :"+u3);

			// The data will not enter the database
			sqlSession.commit();
		}catch (Exception ex) {
			ex.printStackTrace();
		}finally{ MyBatisUtils.closeSqlSession(sqlSession); }}Copy the code

If a CUD operation is involved, the cache disappears automatically and the query is requeried

Console running result description

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,1.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1First query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
DEBUG [main] - ==>  Preparing: UPDATE sys_user SET status = ? WHERE user_id=? 
DEBUG [main] - ==> Parameters: 2(Integer), - 999.(Integer)
DEBUG [main] - < ==    Updates: 1
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1Second query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]

Copy the code

Mybatis level 2 cache

Mybatis level 2 cache is not enabled by default, so I test the following code to see how it works

	@Test
	public voidQuery _ No level 2 cache tests (){// The first SqlSession
		SqlSession sqlSession1 = null;
		// The second SqlSession
		SqlSession sqlSession2 = null;
		try {
			sqlSession1 = MyBatisUtils.getSqlSession();

			sqlSession2 = MyBatisUtils.getSqlSession();

			User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("SqlSession1 query."+u1);

			User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("SqlSession2 query."+u2);

		} finally{ MyBatisUtils.closeSqlSession(sqlSession1); MyBatisUtils.closeSqlSession(sqlSession2); }}Copy the code

Console running result description

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1SqlSession1 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
Tue Sep 05 16:20:42 CST 2017 WARN: Establishing SSL connection without server'identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL Connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false.or set useSSL=true and provide truststore for server certificate verification.
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1SqlSession2 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]

Copy the code

Two sessions are used to query the User with id -999 respectively, so Mybatis interacts with the database twice, which indicates that mybatis has not enabled the level 2 cache now, so we need to manually enable it.

Step 1: Serialize persistent classes

Java class to implement Serializable interface, otherwise MyBatis level 2 cache is not available

public class User implements java.io.Serializable{
	private static final long serialVersionUID = 1L;
}
Copy the code

Step 2: Manually enable level 2 cache

The principle here is that if level 2 caching is enabled, data from the SQLSession level 1 cache will be added to the level 2 cache of namespace after SQLSession is disabled.

Mybatis -config. XML configuration information

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
Copy the code

CacheEnabled defaults to true, so it can be ignored

Usermapper. XML starts the second level cache label

<mapper namespace="com.shxt.dao.UserDao">
    <! -- Enable level 2 cache -->
    <cache></cache>
   <! -- Omitted part of code -->
</mapper>
Copy the code

Step 3: Test the code

		@Test
	public voidQuery _ Level 2 Cache test () {// The first SqlSession
		SqlSession sqlSession1 = null;
		// The second SqlSession
		SqlSession sqlSession2 = null;

		sqlSession1 = MyBatisUtils.getSqlSession();

		sqlSession2 = MyBatisUtils.getSqlSession();

		User u1 = sqlSession1.selectOne(UserDao.class.getName() + ".load", -999);
		System.out.println("SqlSession1 query." + u1);
		MyBatisUtils.closeSqlSession(sqlSession1);/ / close

		User u2 = sqlSession2.selectOne(UserDao.class.getName() + ".load", -999);
		System.out.println("SqlSession2 query." + u2);
		MyBatisUtils.closeSqlSession(sqlSession2);

	}
Copy the code

Console running result description

DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1SqlSession1 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5SqlSession2 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
Copy the code

By default, the cache is flushed when sqlSession commits

	@Test
	public voidQuery _ Level 2 Cache test (){// The first SqlSession
		SqlSession sqlSession1 = null;
		// The second SqlSession
		SqlSession sqlSession2 = null;
		try {
			sqlSession1 = MyBatisUtils.getSqlSession();

			sqlSession2 = MyBatisUtils.getSqlSession();

			User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("SqlSession1 query."+u1);
			sqlSession1.commit();// Force refresh

			User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load", -999);
			System.out.println("SqlSession2 query."+u2);

		} finally{ MyBatisUtils.closeSqlSession(sqlSession1); MyBatisUtils.closeSqlSession(sqlSession2); }}Copy the code

In the select statement:

  • The default value for flushCache is false, which means that the local cache and secondary cache will not be flushed whenever a statement is called.
  • The default value of useCache is true, indicating that the result of this statement will be cached at level 2.

Modify the mapping file as follows

<mapper namespace="com.shxt.dao.UserDao">
    <! -- Enable level 2 cache -->
    <cache></cache>
    
   
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    <! -- flushCache="true" -->
    <select id="load" parameterType="int" resultMap="BaseResultMapper" flushCache="true">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
</mapper>
Copy the code

Run the test method that was committed before the commit again with the result:

DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1SqlSession1 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: - 999.(Integer)
TRACE [main] - < ==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - < ==        Row: - 999., Super, super, Liu Wenming,2.201707 -- 30 09:50:18.0.- 100.
DEBUG [main] - < ==      Total: 1SqlSession2 query:User [user_id=- 999., account=super, password=super, user_name=Wenming Liu, Status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=- 100.]

Copy the code

For INSERT, UPDATE, delete statements:

  • FlushCache defaults to true, which means that any time a statement is called, the local cache and the second-level cache will be flushed.
  • The useCache property is not present in this case

conclusion

Level 1 cache

  1. The default open
  2. It must be the same session. If the session object is already closed (), it will not be used
  3. The query conditions must be consistent
  4. Session.cleancache () was not executed; Clear the cache
  5. No add, delete, or change operations have been performed (these operations clean up the cache)

The second level cache

1. The default value is set in mybatis-config. XML

<settings> 
        <setting name="cacheEnabled" value="true" /> 
</settings>
Copy the code

2. Manually enable adding in mapper. XML

<mapper namespace="com.shxt.dao.UserDao">
    <! -- Enable level 2 cache -->
    <cache></cache>
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    <! -- flushCache="true" -->
    <select id="load" parameterType="int" resultMap="BaseResultMapper">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
</mapper>
Copy the code

3. All select statements in the mapping statement file will be cached. 4. Map all insert, UPDATE, and DELETE statements in the statement file to flush the cache. The Least Recently Used (LRU) algorithm is Used to retrieve the cache. 6. The cache is refreshed at specified intervals. 7. Cache stores 1024 objects

<cache 
eviction="FIFO"// The collection policy is first-in, first-outflushInterval="60000"// Automatic refresh time60s
size="512"// Maximum cache512Reference objectsreadOnly="true"/>/ / read-onlyCopy the code

MyBatis cache doesn’t make much sense.

A. In the face of A certain amount of data, the built-in cache is useless; B. MyBatis framework is not good at caching query result sets, it should concentrate on SQL Mapper. It makes more sense to use the framework’s Application to build caches, such as OSCache, Memcached, etc.