Mybatis cache details

Posted by onicsoft on Wed, 02 Mar 2022 16:52:25 +0100

MyBatis cache

9.1. MyBatis cache understanding

Like most persistence layer frameworks, MyBatis also provides L1 and L2 cache support

1. L1 cache: be based on PerpetualCache of HashMap Local cache with storage scope of Session,When Session
flush  or close After that, the Session All in Cache It will be empty.
2. The mechanism of L2 cache is the same as that of L1 cache, which is also adopted by default PerpetualCache,HashMap Storage, the difference is
 Its storage scope is Mapper(Namespace),And you can customize the storage source, such as Ehcache. 
3. For the cache data update mechanism, when a scope(L1 cache Session/L2 cache Namespaces)The is carried out
C/U/D After the operation, all items in the scope will be selected by default select The cache in will be deleted clear. 

9.2. Mybatis L1 cache

How is the first level cache organized in MyBatis? (that is, how is the cache organized in SqlSession?)
Since MyBatis uses the SqlSession object to represent a database session, the session level L1 cache should also be controlled in SqlSession.

SqlSession is just an external interface of MyBatis. SqlSession gives its work to the role of Executor, which is responsible for various operations on the database.
When a SqlSession object is created, MyBatis will create a new Executor for the SqlSession object, and the Cache information is maintained in the Executor executor. MyBatis encapsulates the Cache and Cache related operations into the Cache interface.
The relationship among SqlSession, Executor and Cache is shown in the following class diagram:

As shown in the above class diagram, the implementation class BaseExecutor of the Executor interface has an implementation class PerpetualCache of the Cache interface. For the BaseExecutor object, it will use the PerpetualCache object to maintain the Cache.

To sum up, the relationship among SqlSession object, Executor object and Cache object is shown in the following figure:

The implementation principle of perpetual cache is actually very simple. Its internal is through a simple HashMap < K, V >
There are no other restrictions. The following is the implementation code of PerpetualCache:

[](javascript:void(0)😉

package org.apache.ibatis.cache.impl;
 
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
 
public class PerpetualCache implements Cache {
 
  private String id;
 
  <strong>private Map<Object, Object> cache = new HashMap<Object, Object>();</strong>
 
  private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 
  public PerpetualCache(String id) {
    this.id = id;
  }
 
  public String getId() {
    return id;
  }
 
  public int getSize() {
    return cache.size();
  }
 
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
 
  public Object getObject(Object key) {
    return cache.get(key);
  }
 
  public Object removeObject(Object key) {
    return cache.remove(key);
  }
 
  public void clear() {
    cache.clear();
  }
 
  public ReadWriteLock getReadWriteLock() {
    return readWriteLock;
  }
 
  public boolean equals(Object o) {
    if (getId() == null) throw new CacheException("Cache instances require an ID.");
    if (this == o) return true;
    if (!(o instanceof Cache)) return false;
 
    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }
 
  public int hashCode() {
    if (getId() == null) throw new CacheException("Cache instances require an ID.");
    return getId().hashCode();
  }
 
}

[](javascript:void(0)😉

How long is the lifecycle of the L1 cache?
a. When mybatis starts a database session, it will create a new SqlSession object. There will be a new Executor object in the SqlSession object, and a new perpetual cache object will be held in the Executor object; When the session object and the Executor object are released, the session object and the Executor object are also released.
b. If SqlSession calls the close() method, the level-1 cache object will be released, and the level-1 cache will not be available;
c. If SqlSession calls clearCache(), the data in the PerpetualCache object will be cleared, but the object can still be used;
d. Any update operation (update(), delete(), insert()) in sqlsession will clear the data of the perpetual cache object, but the object can continue to be used;

Workflow of SqlSession L1 cache:
1. For a query, build a key value according to statementid, params and rowbounds, and Cache the Cache results stored by the corresponding key value in the Cache according to the key value;
\2. Judge whether the data obtained from the Cache according to the specific key value is empty, that is, whether it is hit;
\3. If it hits, the cache result will be returned directly;
\4. If missed:
4.1 query the data in the database and get the query results;
4.2 store the key and the query result as key and value pairs in the Cache respectively;
4.3. Return the query result;
\5. End.

key value of Map in Cache: CacheKey

The core implementation of Cache is actually a Map, which takes the characteristic value used in this query as the key and stores the query result as the value in the Map. How to determine the eigenvalue of a query? How to judge whether two queries are identical? How to determine the key value in the Cache?
MyBatis believes that for two queries, if the following conditions are exactly the same, they are considered to be the same two queries:
\1. The statementId passed in
For MyBatis, if you want to use the passed statementId, you must need a statementId, which represents what kind of Sql you will execute;
\2. The result range in the result set required during query (the result range is represented by rowBounds.offset and rowBounds.limit);
\3. The generated by this query will eventually be passed to JDBC Java Sql. Sql statement string of Preparedstatement (boundSql.getSql())
\4. Pass to Java sql. Statement parameter value to be set
To sum up, the CacheKey is determined by the following conditions: statementId + rowBounds + SQL passed to JDBC + parameter value passed to JDBC

1) Propose requirements:

Query the corresponding user record object according to the id

2). Prepare database tables and data

[](javascript:void(0)😉

CREATE TABLE c_user(
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(20),
  age INT
);
INSERT INTO c_user(NAME, age) VALUES('Tom', 12);
INSERT INTO c_user(NAME, age) VALUES('Jack', 11);

[](javascript:void(0)😉

3). Create entity class of table

public class User implements Serializable{
private int id;
private String name;
private int age;
}

4). userMapper.xml

[](javascript:void(0)😉

<?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">
<mapper namespace="com.atguigu.mybatis.test8.userMapper">
  <select id="getUser" parameterType="int" resultType="_CUser">
    select * from c_user where id=#{id}
  </select>
  <update id="updateUser" parameterType="_CUser">
  update c_user set name=#{name}, age=#{age} where id=#{id}
  </update>
</mapper>

[](javascript:void(0)😉

5). test

[](javascript:void(0)😉

/*
* L1 cache: Session level cache (enabled by default)
*/
@Test
public void testCache1() {
SqlSession session = MybatisUtils.getSession();
String statement = "com.atguigu.mybatis.test8.userMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println(user);
/*
* The L1 cache will be used by default
*/
/*
user = session.selectOne(statement, 1);
System.out.println(user);
*/
/*
1. It must be the same session. If the session object has been closed (), it cannot be used
*/
/*
session = MybatisUtils.getSession();
user = session.selectOne(statement, 1);
System.out.println(user);
*/
/*
2. The query criteria are the same
*/
/*
user = session.selectOne(statement, 2);
System.out.println(user);
*/
/*
3. No session has been executed Clearcache() clean cache
*/
/*
session.clearCache();
user = session.selectOne(statement, 2);
System.out.println(user);
*/
/*
4. Add, delete and modify operations have been performed (these operations will clean up the cache)
*/
/*
session.update("com.atguigu.mybatis.test8.userMapper.updateUser",
new User(2, "user", 23));
user = session.selectOne(statement, 2);
System.out.println(user);
*/
}

[](javascript:void(0)😉

9.3. Mybatis L2 cache

The overall design of MyBatis cache mechanism and the working mode of L2 cache
The secondary cache of MyBatis is the Application level cache, which can improve the efficiency of database query and improve the performance of Application.

When opening a session, a SqlSession object will use an Executor object to complete the session operation. The key of MyBatis's secondary cache mechanism is to write about this Executor object. If the user configures "cacheEnabled=true", MyBatis will add a decorator to the Executor object when creating the Executor object for the SqlSession object: cacheingexecution. At this time, the SqlSession uses the cacheingexecution object to complete the operation request.

For a query request, the caching executor will first judge whether the query request has cache results in the secondary cache at the Application level,

If there are query results, the cached results will be returned directly;

If there is no in the cache, then give it to the real Executor object to complete the query operation,

After that, the cacheingexecution will put the query results returned by the real Executor into the cache,

Then return to the user.

Caching Executor is the decorator of Executor to enhance the function of Executor and make it have the function of cache query. Here, the decorator mode in design mode is used.

Division of MyBatis L2 cache
MyBatis does not simply have one Cache object for the entire Application,
It subdivides the Cache, that is, Mapper level, that is, each Mapper can have a Cache object, as follows:
a. Allocate a Cache object for each Mapper (using node configuration);
b. Multiple mappers share a Cache object (using node configuration);

Necessary conditions for using L2 cache
MyBatis supports L2 cache with fine granularity. It specifies whether a query statement uses L2 cache.
Although it is configured in Mapper and a Cache object is assigned to this Mapper,
This does not mean that all the results found by using the query statements defined in Mapper will be placed in the Cache object,
We must specify whether a selection statement in Mapper supports caching, that is, if useCache = "true" is configured in the node, Mapper will support caching for this Select query,

In short, if you want a Select query to support L2 caching, you need to ensure that:
1.MyBatis supports the master switch of L2 cache: the global configuration variable parameter cacheEnabled=true
2. The Mapper where the select statement is located is configured with or nodes and is valid
3. The parameter useCache=true of the select statement

Choice of L2 cache implementation
MyBatis is very flexible in the design of L2 Cache. It implements a series of Cache implementation classes internally and provides various Cache refresh strategies, such as LRU, FIFO, etc;
In addition, MyBatis also allows users to customize the implementation of cache interface. Users need to implement org apache. ibatis. cache. Cache interface,
Configure the type attribute of the node on the Cache class;
In addition, MyBatis also supports integration with third-party memory cache libraries such as Memecached. In short, there are three options for using MyBatis's L2 cache:
1. The cache implementation provided by mybatis itself;
2. User defined Cache interface implementation;
3. Integration with third-party memory cache library;

Implementation of L2 cache provided by MyBatis
MyBatis itself provides a rich and powerful implementation of L2 Cache. It has a series of Cache interface decorators, which can meet various strategies for Cache operation and update.
MyBatis defines a large number of Cache decorators to enhance the function of Cache cache, as shown in the following class diagram.
For each Cache, there is a capacity limit. MyBatis provides various strategies to control the capacity of the Cache and refresh and replace the data in the Cache.
MyBatis mainly provides the following refresh and replacement strategies:
LRU:(Least Recently Used), the least recently used algorithm, that is, if the capacity in the cache is full, the cache records that have been used less recently will be cleared, and then new records will be added;
FIFO:(First in first out), first in first out algorithm. If the capacity in the cache is full, the data that enters the cache first will be cleared;
Scheduled: Specifies the time interval emptying algorithm, which will empty the data in the Cache at a specified time interval;

Precautions when using Cache / avoid using L2 Cache

matters needing attention
\1. The cache can only be used on the table of [single table operation only]
It is not only necessary to ensure that this table has only single table operation in the whole system, but also all operations related to this table must be in one namespace.
\2. Use cache when you can ensure that the query is much larger than insert, update and delete operations
There is no need to say more about this. Everyone should be clear about it. Remember, this needs to be guaranteed under the premise of 1!

Avoid using L2 cache. The benefits of L2 cache are far less than the hidden hazards.
1. The cache is based on the namespace, and the operations under different namespaces do not affect each other.
2. Insert, update and delete operations will empty all caches in the namespace.
3. In the code generated by MyBatis Generator, each table is independent, and each table has its own namespace.

Some operations on a table are not performed in its independent namespace.
For example, in usermapper There are most operations on the user table in XML. But in a xxxmapper In XML, there are also operations for the user single table
This will result in inconsistent user data under the two namespaces. If in usermapper The cache is refreshed in XML,
In xxxmapper The cache in XML is still valid. If there is a single table query for user, the results using the cache may be incorrect.
The more dangerous situation is in xxxmapper When XML performs insert, update and delete operations, it will cause usermapper Various operations in XML are full of unknowns and risks.

Multi table operations must not use caching
First of all, no matter which namespace the multi table operation is written to, there will always be a case that a table is not in this namespace.

Finally, it is better to abandon the L2 cache and use a controllable cache in the business layer instead.

1). Add one in usermapper xml

<mapper namespace="com.atguigu.mybatis.test8.userMapper">
<cache/>

2). test

[](javascript:void(0)😉

/*
* Test L2 cache
*/
@Test
public void testCache2() {
  String statement = "com.atguigu.mybatis.test8.userMapper.getUser";
  SqlSession session = MybatisUtils.getSession();
  User user = session.selectOne(statement, 1);
  session.commit();  System.out.println("user="+user);
  SqlSession session2 = MybatisUtils.getSession();
  user = session2.selectOne(statement, 1);
  session.commit();
  System.out.println("user2="+user);
}

[](javascript:void(0)😉

3). Supplementary notes

All select statements in the mapping statement file will be cached.
The update statement and the delete statement refresh all the mappings in the cache.
The cache will be reclaimed using the Least Recently Used (LRU) algorithm.
According to the schedule (such as no Flush Interval, there is no refresh interval), the cache will not be refreshed in any chronological order.
The cache stores 1024 references to a list collection or object, regardless of what the query method returns.
The cache will be regarded as a read/write cache, which means that object retrieval is not shared and can be installed
Modified by callers all over the world without interfering with potential modifications made by other callers or threads

<cache
eviction="FIFO" //The recycling strategy is first in first out
flushInterval="60000" //Automatic refresh time 60s
size="512" //Cache up to 512 reference objects
readOnly="true"/> //read-only

javascript:void(0)😉

public class User {
  private int id;
  private String name;
  private Date birthday;
  private double salary;
  //set,get method
}

[](javascript:void(0)😉

3). DAO interface: UserMapper (XXXMapper)

[](javascript:void(0)😉

public interface UserMapper {
  void save(User user);
  void update(User user);
  void delete(int id);
  User findById(int id);
  List<User> findAll();
}

[](javascript:void(0)😉

4). SQL mapping file: usermapper xml

[](javascript:void(0)😉

<?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">
<mapper namespace="com.atguigu.mybatis.test9.UserMapper">
  <resultMap type="User" id="userResult">
    <result column="user_id" property="id"/>
    <result column="user_name" property="name"/>
    <result column="user_birthday" property="birthday"/>
    <result column="user_salary" property="salary"/>
  </resultMap>
<!-- Get after inserting data id -->
<insert id="save" keyColumn="user_id" keyProperty="id" useGeneratedKeys="true">
  insert into s_user(user_name,user_birthday,user_salary) values(#{name},#{birthday},#{salary})
</insert>
<update id="update">
  update s_user
  set user_name = #{name},
  user_birthday = #{birthday},
  user_salary = #{salary}
  where user_id = #{id}
</update>
<delete id="delete">
  delete from s_user where user_id = #{id}
</delete>
<select id="findById" resultMap="userResult">
  select * from s_user where user_id = #{id}
</select>
<select id="findAll" resultMap="userResult">
  select * from s_user
</select>
</mapper>

[](javascript:void(0)😉

5). Database connection file: dB properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

6). spring configuration file: beans xml

[](javascript:void(0)😉

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<!--  relation properties  file -->
<context:property-placeholder location="db.properties" />
<!--  data source -->
<bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  p:driverClassName="${jdbc.driverClassName}"
  p:url="${jdbc.url}"
  p:username="${jdbc.username}"
  p:password="${jdbc.password}"/>
<!--
class:  Specifies the name used to create the sqlSession  Factory
dataSource-ref:  Data source used
typeAliasesPackage: Automatically scanned entity class package
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
  p:dataSource-ref="dataSource"
  p:typeAliasesPackage="org.monmday.springmybatis.domian"/>
<!--
class :  Specify automatic scanning xxxMapper.xml  Class of mapping file
basePackage:  Configuration package for automatic scanning
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
  p:basePackage="org.monmday.springmybatis.mappers"
  p:sqlSessionFactoryBeanName="sqlSessionFactory"/>
<!--  transaction management -->
<bean id="txManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
  p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="txManager" />
</beans>

[](javascript:void(0)😉

7). test

[](javascript:void(0)😉

@RunWith(SpringJUnit4ClassRunner.class) //Using the spring test framework
@ContextConfiguration("/beans.xml") //load configuration
public class SMTest {
  @Autowired //injection
  private UserMapper userMapper;
  @Test
  public void save() {
    User user = new User();
    user.setBirthday(new Date());
    user.setName("marry");
    user.setSalary(300);
    userMapper.save(user);
    System.out.println(user.getId());
  }
  @Test
  public void update() {
    User user = userMapper.findById(2);
    user.setSalary(2000);
    userMapper.update(user);
  }
  @Test
  public void delete() {
    userMapper.delete(3);
  }
  @Test
  public void findById() {
    User user = userMapper.findById(1);
    System.out.println(user);
  }
  @Test
  public void findAll() {
    List<User> users = userMapper.findAll();
    System.out.println(users);
  }
}

y");
    user.setSalary(300);
    userMapper.save(user);
    System.out.println(user.getId());
  }
  @Test
  public void update() {
    User user = userMapper.findById(2);
    user.setSalary(2000);
    userMapper.update(user);
  }
  @Test
  public void delete() {
    userMapper.delete(3);
  }
  @Test
  public void findById() {
    User user = userMapper.findById(1);
    System.out.println(user);
  }
  @Test
  public void findAll() {
    List users = userMapper.findAll();
    System.out.println(users);
  }
}

[![Copy code](https://img-blog.csdnimg.cn/img_convert/0df0ac177befa5b6fb7ea2ac9fef09d9.gif)](javascript:void(0);)

Topics: Java Mybatis xml server