Detailed explanation of MyBatis two-level caching mechanism

Caching is an important means to improve the performance of hardware and software systems; on the hardware level, modern advanced CPU s have three-level caching, and MyBatis also provides a caching mechanism, which can greatly improve our query performance.

Level 1 cache

Mybatis supports caching, but by default, it only opens the first level cache, which is also called local cache, relative to the same SqlSession. So when the parameters and SQL are exactly the same, we use the same SqlSession object to call a Mapper method, often only execute SQL once, because after the first query using SelSession, MyBatis will put it in the cache, and when the query is later, if there is no declaration to refresh, and the cache does not exceed. In this case, SqlSession will retrieve the currently cached data without sending SQL to the database again.

Why use first-level caching? You don't need to say much about it. But there are a few more questions we should pay attention to.

Life Cycle of Level 1 Cache

  1. When MyBatis opens a database session, it creates a new SqlSession object and a new Executor object in the SqlSession object. A new PerpetualCache object is held in the Executor object; when the session ends, the SqlSession object and its internal Executor object, as well as the PerpetualCache object, are also released.
  2. If SqlSession calls the close() method, the first-level cache PerpetualCache object will be released and the first-level cache will not be available.
  3. If SqlSession calls clearCache(), the data in the PerpetualCache object is cleared, but the object is still available. -
  4. Any update(), delete(), insert () performed in SqlSession empties the data of the PerpetualCache object, but the object can continue to be used.

How to judge that two queries are exactly the same?

mybatis believes that for two queries, if the following conditions are exactly the same, then they are considered to be exactly the same two queries.

  • The incoming statementId
  • Range of results in the result set required for query
  • The result of this query is the Sql statement string (boundSql.getSql()) passed to JDBC java.sql.Preparedstatement.
  • Parameter values passed to java.sql.Statement

Test of Level 1 Cache

public class FirstCachedTest {
    @Test
    public void test() throws Exception {
        InputStream resource = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession session = sessionFactory.openSession();
        ArticleMapper mapper = session.getMapper(ArticleMapper.class);
        Article article1 = mapper.getArticleById(1L);
        Article article2 = mapper.getArticleById(1L);
        System.out.println(article1 == article2);  //Output true
    }
}

Implementation results:

It's important to note that this is a cache test conducted when MyBatis is used alone. If MyBatis is integrated with Spring, the primary cache of MyBatis may fail. See also for details. https://blog.csdn.net/ctwy291314/article/details/81938882

Level 2 cache

MyBatis's secondary cache is Application-level cache, which can improve the efficiency of database query and Application performance.

Secondary cache at SqlSession Factory level is not opened by default. The opening of secondary cache needs to be configured. When implementing secondary cache, MyBatis requires POJO s returned to be serializable.

Configuration steps of secondary cache

Step 1: Configure SqlMapConfig.xml (omitable)

We essentially need to open any cache that all mappers in the configuration file have configured in the global configuration file, namely the cacheEnabled property, but the default value of this property is true, so we can actually omit this step.

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

Step 2: Configure the mapping file

cache tag configuration

To enable global secondary caching, just add a line to your SQL mapping file:

<cache/>

The effect of this simple statement is as follows:

  • The results of all select statements in the mapping statement file will be cached.
  • All insert, update, and delete statements in the mapping statement file refresh the cache.
  • Caching uses the Least Current Used (LRU) algorithm to clear unnecessary caches.
  • The cache does not refresh regularly (that is, there is no refresh interval).
  • The cache stores 1024 references to a list or object, regardless of which query method returns.
  • Caches are considered read/write caches, which means that the acquired objects are not shared and can be safely modified by the caller without interfering with potential modifications made by other callers or threads.
Attributes of the cache tag:
  • eviction: Set the cache clearance policy with the default value of LRU
    • LRU - Minimum Recent Use: Remove objects that have not been used for the longest time.
    • FIFO - First in, first out: Remove objects in the order they enter the cache.
    • SOFT - Soft Reference: Remove objects based on garbage collector status and soft reference rules.
    • WEAK - Weak Reference: Remove objects more actively based on garbage collector status and weak reference rules.
  • Flush Interval: (refresh interval) Property can be set to any positive integer, and the set value should be a reasonable amount of time in milliseconds. By default, there is no refresh interval, and the cache refreshes only when the statement is invoked.
  • Size: (Number of references) Attributes can be set to any positive integer, be aware of the size of the object to be cached and the memory resources available in the running environment. The default value is 1024.
  • readOnly: (read-only) properties can be set to true or false (default). A read-only cache returns the same instance of the cached object to all callers. Therefore, these objects cannot be modified. This provides considerable performance improvements. The read-write cache returns a copy of the cached object (through deserialization). It's slower, but safer, so the default value is false.
SQL Statement Label Configuration

Cache configuration and cache instances are bound to the namespace of the SQL mapping file. Therefore, all statements and caches in the same namespace will be bound together through the namespace. Each statement can customize the way it interacts with the cache or completely exclude them from the cache, which can be achieved by using two simple attributes on each statement. By default, statements are configured as follows:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

Given that this is the default behavior, it is clear that you should never explicitly configure a statement in this way. But if you want to change the default behavior, you just need to set the flushCache and useCache attributes. For example, in some cases you might want the results of a particular select statement to be excluded from the cache, or you might want a select statement to empty the cache. Similarly, you may want some update statements to be executed without refreshing the cache.

<select id="getArticleById" resultMap="articleMap" parameterType="long" useCache="true" flushCache="false">
	select * from article where id = #{id}
</select>

Step 3: Let the entity class implement the Serializable interface

Because the < cache/> readOnly tag defaults to false, MyBatis's read-write cache is accomplished by serialization and deserialization.

Level 2 cache testing

public class FirstCachedTest {
    @Test
    public void test() throws Exception {
        InputStream resource = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession session = sessionFactory.openSession();
        ArticleMapper mapper = session.getMapper(ArticleMapper.class);
        Article article1 = mapper.getArticleById(1L);
        session.close();
        //Open a new SqlSession to read the secondary cache (global cache) in different SqlSessions
        session=sessionFactory.openSession();
        ArticleMapper mapper2 = session.getMapper(ArticleMapper.class);
        Article article2 = mapper2.getArticleById(1L);
        System.out.println(article1 == article2);
    }
}

Implementation results:

Notes for Level 2 Cache

For queries with more commit s and less real-time requirements of users, mybatis secondary caching technology is used to reduce database access and improve access speed. However, secondary cache can not be abused, and there are many drawbacks of secondary cache, as can be seen from MyBatis default secondary cache is closed. The secondary cache is built under the same namespace. If there may be more than one namespace for table operation query, the data obtained is wrong (dirty reading).

Examples: articles and tags, ArticleMapper, TagMapper. When we query articles, we need to query the corresponding tags of articles. Then the tag information is cached in the namespace corresponding to ArticleMapper. At this time, some people need to modify the basic information of Tag, that is, to modify it in the namespace of TagMapper. It will not affect the caching of ArticleMapper. So, the tag information is cached in the namespace corresponding to ArticleMapper. When you look up the article data again, you get the cached data, which is actually outdated.

Level 2 Cache Dirty Reading Test

Article and Tag are one-to-many relationships, in which Aticle is one and Tag is many.

ArticleMapper

<mapper namespace="com.tjd.spring_mybatis_plus.mapper.ArticleMapper">
    <cache />
    <resultMap id="articleMap" type="Article">
        <id column="id" property="id"></id>
        <result column="title" property="title"></result>
        <result column="content" property="content"></result>
        <collection property="tags" ofType="Tag" column="id" select="com.tjd.spring_mybatis_plus.mapper.TagMapper.getTagsByArticleId"></collection>
    </resultMap>
    <select id="getArticleById" resultMap="articleMap" parameterType="long" useCache="true">
      select * from article where id = #{id}
    </select>
</mapper>

TagMapper

<mapper namespace="com.tjd.spring_mybatis_plus.mapper.TagMapper">
    <cache/>
    <resultMap id="tagMap" type="Tag">
        <id column="id" property="id"></id>
        <result column="content" property="content"></result>
        <association  property="article" column="article_id" javaType="Article" select="com.tjd.spring_mybatis_plus.mapper.ArticleMapper.getArticleById"></association>
    </resultMap>
    <update id="updateTag" parameterType="Tag">
        update tag  set content=#{content} where id=#{id}
    </update>
    <select id="getTagsByArticleId" parameterType="long" resultMap="tagMap">
        select * from tag where article_id=#{article_id}
    </select>
</mapper>

Test code

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SecondCachedErrorTest {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private TagMapper tagMapper;

    @Test
    public void test() throws IOException {
        //For the first query, the queried aticle object is cached under the namespace of ArticleMapper
        Article article = articleMapper.getArticleById(1L);
        Tag tag = article.getTags().get(0);
        tag.setContent("dasdas");
        //Update Tag, then the secondary cache under TagMapper is refreshed (cleared)
        tagMapper.updateTag(tag);
        //Query Article again, and get cached data, and the associated tag data is outdated
        Article article2 = articleMapper.getArticleById(1L);
        //The cache in the namespace corresponding to TagMapper is refreshed (cleared) when updated, so the query results are correct.
        List<Tag> tags = tagMapper.getTagsByArticleId(1L);
    }
}

Based on the above tests, we understand that two problems need to be considered when we want to use secondary caching:

  • Operations and queries on this table are all in the same namespace. If other namespaces have operations, dirty reading of data will occur.
  • For queries of associated tables, all associated tables must operate in the same namespace.
  • In the case of multi-table queries, it is recommended not to use secondary caching.

Priority of two-level cache

If the two-level cache is opened at the same time, the second-level cache has a higher priority than the first-level cache, that is, when performing database query operations, the content of the second-level cache is read first.

Reference to:

https://blog.csdn.net/ctwy291314/article/details/81938882

https://www.cnblogs.com/happyflyingpig/p/7739749.html

https://www.cnblogs.com/yuluoxingkong/p/8205858.html

http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

Blogger's personal home page, interested partners can pay attention to: www.bigcoder.cn (In some areas, the network speed is relatively slow, you can wait a moment.)

Tags: Mybatis SQL Session Database

Posted on Sun, 04 Aug 2019 22:43:38 -0700 by charlestide