MyBatis notes: MyBatis cache / madness

Posted by rolwong on Sun, 20 Feb 2022 19:58:59 +0100

1. Cache

What is Cache?

  • There is temporary data in memory.

  • Put the data frequently queried by users in the cache (memory), and users do not need to query the data from the disk (relational database data file) but from the cache, so as to improve the query efficiency and solve the performance problem of high concurrency system.

2. Why cache?

  • Reduce the number of interactions with the database, reduce system overhead and improve system efficiency.

3. What kind of data can be cached?

  • Data that is frequently queried and not frequently changed.

2. Mybatis cache

  • MyBatis includes a very powerful query caching feature, which can easily customize and configure the cache. Caching can greatly improve query efficiency.

  • Two levels of cache are defined by default in MyBatis system: L1 cache and L2 cache

    • By default, only L1 cache is on. (SqlSession level cache, also known as local cache)

    • The second level cache needs to be manually configured.

    • Custom L2 Cache: in order to improve scalability, MyBatis defines the Cache interface Cache. We can customize the L2 Cache by implementing the Cache interface

2.1. L1 cache

L1 cache is also called local cache:

  • The data queried during the same session with the database will be placed in the local cache.

  • In the future, if you need to obtain the same data, you can get it directly from the cache. You don't have to query the database again;

  • Get valid between session and close(). If you leave the close, it will be invalid

    public void testQueryBlog(){
        SqlSession session = MybatisUtils.getSqlSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);

        Map map = new HashMap<String, String>();
        map.put("title", "");
        map.put("author", "Madness theory");

        List<Blog> blogs = mapper.queryBlog(map);
        blogs.stream().forEach(System.out::print);

        session.commit();
        session.close();
    }

2.1.1 L1 cache test

1. Add logs in mybatis to facilitate the test results

2. Write interface method

//Query user by id
User queryUserById(@Param("id") int id);

3. Mapper file corresponding to interface

<select id="queryUserById" resultType="user">
  select * from user where id = #{id}
</select>

4. Testing

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}

5. Result analysis

2.1.2. Four situations of L1 cache invalidation

  • The first level cache is a SqlSession level cache, which is always on, and we can't close it;
  • L1 cache invalidation: the current L1 cache is not used. The effect is that you need to send another query request to the database!

1. sqlSession is different

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
   session2.close();
}

Observation: two SQL statements were sent!

Conclusion: the caches in each sqlSession are independent of each other

2. The sqlSession is the same, but the query conditions are different

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(2);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}

Observation: two SQL statements were sent! Very normal understanding

Conclusion: this data does not exist in the current cache

3. sqlSession is the same. Add, delete and modify operations are performed between the two queries!

Increase method

//Modify user
int updateUser(Map map);

Write SQL

<update id="updateUser" parameterType="map">
  update user set name = #{name} where id = #{id}
</update>

test

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   HashMap map = new HashMap();
   map.put("name","kuangshen");
   map.put("id",4);
   mapper.updateUser(map);

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}

Observation result: the query is re executed after adding, deleting and modifying in the middle

Conclusion: addition, deletion and modification may affect the current data

4. sqlSession is the same. Manually clear the L1 cache

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   session.clearCache();//Manually clear cache

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}

Summary:

  • The L1 cache is enabled by default and is only valid in one SqlSession, that is, the interval from getting the connection to closing the connection is valid.
  • The first level cache is a map

2.2. L2 cache

  • L2 cache is also called global cache. The scope of L1 cache is too low, so L2 cache was born

  • Cache based on namespace level, one namespace corresponds to one L2 cache;

  • Working mechanism

    • When a session queries a piece of data, the data will be placed in the first level cache of the current session;

    • If the current session is closed, the L1 cache corresponding to this session is gone; But what we want is that the session is closed and the data in the L1 cache is saved to the L2 cache;

    • The new session query information can get the content from the L2 cache;

    • The data found by different mapper s will be placed in their corresponding cache (map);

Examples of working mechanism:

As follows, all sql in the namespace uses this cache, which is larger than the first level cache.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace Bind a corresponding Dao/Mapper Interface: for example UserDao -->
<mapper namespace="com.crane.mapper.BlogMapper">

    <insert id="addBlog" parameterType="com.crane.pojo.Blog">
        insert into blog (id, title, author, create_time, views)
        values (#{id},#{title},#{author},#{createTime},#{views});
    </insert>

    <select id="queryBlog" parameterType="map" resultType="com.crane.pojo.Blog">
        select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
    </select>

    <select id="queryBlogForEach" parameterType="map" resultType="com.crane.pojo.Blog">
        select * from blog
        <where>
            <foreach collection="ids" index="id" open="and (" close=")" separator="or">
                id = #{id}
            </foreach>
        </where>
    </select>

</mapper>

2.2.1 usage steps of L2 cache

1. Open global cache [mybatis config. XML]

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

2. Go to every mapper XML is configured to use L2 cache, which is very simple; [xxxMapper.xml]

<cache/>
Official example = = = = > View official documents
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
This more advanced configuration creates a FIFO cache, which is refreshed every 60 seconds. It can store up to 512 references of the result object or list, and the returned objects are considered read-only. Therefore, modifying them may conflict with callers in different threads.

3. Code test

  • All entity classes implement the serialization interface first

  • Test code

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();

   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   session.close();

   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session2.close();
}

2.2.2 conclusion

  • As long as the L2 cache is enabled, our queries in the same Mapper can get data in the L2 cache

  • The detected data will be put in the first level cache by default

  • Only after the session is committed or closed, the data in the L1 cache will be transferred to the L2 cache

2.2.3 schematic diagram of cache

3,EhCache

Third party cache implementation -- EhCache: View Baidu Encyclopedia

Ehcache is a widely used java distributed cache, which is used for general cache;

To use Ehcache in your application, you need to introduce dependent jar packages

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-ehcache</artifactId>
   <version>1.1.0</version>
</dependency>
stay mapper.xml Use the corresponding cache in

<mapper namespace = "org.acme.FooMapper" >
   <cache type = "org.mybatis.caches.ehcache.EhcacheCache" />
</mapper>
to write ehcache.xml File, if not found when loading/ehcache.xml If there is a problem with the resource or, the default configuration will be used.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
   <!--
      diskStore: Is the cache path, ehcache There are two levels: memory and disk. This attribute defines the cache location of the disk. The parameters are explained as follows:
      user.home – User home directory
      user.dir – User's current working directory
      java.io.tmpdir – Default temporary file path
    -->
   <diskStore path="./tmpdir/Tmp_EhCache"/>
   
   <defaultCache
           eternal="false"
           maxElementsInMemory="10000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="259200"
           memoryStoreEvictionPolicy="LRU"/>

   <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
   <!--
      defaultCache: Default cache policy, when ehcache This cache policy is used when the defined cache cannot be found. Only one can be defined.
    -->
   <!--
     name:Cache name.
     maxElementsInMemory:Maximum number of caches
     maxElementsOnDisk: Maximum number of hard disk caches.
     eternal:Whether the object is permanently valid, but once it is set, timeout Will not work.
     overflowToDisk:Whether to save to disk when the system crashes
     timeToIdleSeconds:Set the allowed idle time of the object before expiration (unit: seconds). Only if eternal=false It is used when the object is not permanently valid. It is an optional attribute. The default value is 0, that is, the idle time is infinite.
     timeToLiveSeconds:Set the allowable survival time of the object before invalidation (unit: seconds). The maximum time is between creation time and expiration time. Only if eternal=false Used when the object is not permanently valid. The default is 0.,That is, the survival time of the object is infinite.
     diskPersistent: Whether to cache virtual machine restart data Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB: This parameter setting DiskStore(Cache size of disk cache). The default is 30 MB. each Cache Each should have its own buffer.
     diskExpiryThreadIntervalSeconds: The running time interval of disk failure thread is 120 seconds by default.
     memoryStoreEvictionPolicy: When reached maxElementsInMemory When restricted, Ehcache The memory will be cleaned up according to the specified policy. The default policy is LRU(Least recently used). You can set it to FIFO(First in first out) or LFU(Less used).
     clearOnFlush: Whether to clear when the amount of memory is maximum.
     memoryStoreEvictionPolicy:The optional strategies are: LRU(Least recently used, default policy) FIFO(First in first out) LFU(Minimum number of visits).
     FIFO,first in first out,This is the most familiar, first in, first out.
     LFU, Less Frequently Used,This is the strategy used in the above example. To put it bluntly, it has always been the least used. As mentioned above, the cached element has a hit Properties, hit The smallest value will be flushed out of the cache.
     LRU,Least Recently Used,The least recently used element in the cache has a timestamp. When the cache capacity is full and it needs to make room for caching new elements, the element with the farthest timestamp from the current time in the existing cache elements will be cleared out of the cache.
  -->

</ehcache>

Reasonable use of cache can greatly improve the performance of our program!

Topics: Java Mybatis