Mybatis-cache cache

Cache

Mybatis cache is divided into first-level cache and second-level cache.
Insert picture description here
Cache is a function that general ORM frameworks provide. The purpose is to improve query efficiency and reduce database pressure. Like Hibernate, MyBatis also has a first-level cache and a second-level cache, and reserves the interface to integrate third-party caches.

Cache architecture:
Insert picture description here

The cache-related classes of MyBatis are in the cache package. There is a Cache interface, and there is only a default implementation class PerpetualCache, which is implemented by HashMap. We can find the true face of this cache through the following categories

DefaultSqlSession

-> BaseExecutor

-> PerpetualCache localCache

->private Map<Object, Object> cache = new HashMap();

In addition, there are many decorators, through which many additional functions can be realized: recycling strategy, log record, regular refresh and so on. But no matter how it is decorated, how many layers of decoration, the basic implementation class (PerpetualCache by default) is finally used. It can be checked by CachingExecutor class Debug.
Insert picture description here

All cache implementation classes can be generally divided into three categories: basic cache, eliminated algorithm cache, and decorator cache.

Insert picture description here

Level 1 cache (local cache):
  Level 1 cache is also called local cache. MyBatis's level 1 cache is cached at the session (SqlSession) level. The first level cache of MyBatis is enabled by default and does not require any configuration. First of all, we have to figure out a problem. In the process executed by MyBatis, there are so many objects involved, then which object should be stored in PerpetualCache for maintenance? If you want to share the first level cache in the same session, this object must be created in SqlSession as an attribute of SqlSession.

There are only two properties in DefaultSqlSession, Configuration is global, so the cache can only be maintained in Executor-SimpleExecutor/ReuseExecutor/BatchExecutor's parent class BaseExecutor holds PerpetualCache in the constructor. In the same session, if the same SQL statement is executed multiple times, the cached result will be directly accessed from within, and no SQL will be sent to the database. However, in different sessions, even if the executed SQL is exactly the same (called with the same parameters of the same method of a Mapper), the first level cache cannot be used.

Whenever we use MyBatis to open a session with the database, MyBatis will create a SqlSession object to represent a database session.

In a session to the database, we may execute the exact same query repeatedly. If we don’t take some measures, every query will query the database, and we will do exactly the same query in a very short time. , Then their results are very likely to be exactly the same, because the cost of querying the database is very high, which may cause a great waste of resources.

In order to solve this problem and reduce the waste of resources, MyBatis will create a simple cache in the SqlSession object representing the session, and cache the results of each query. When the next query is made, if it is determined that there is exactly the same The query will directly fetch the result from the cache and return it to the user without having to perform another database query.

As shown in the figure below, MyBatis will create a local cache in the representation of a session-a SqlSession object. For each query, it will try to find whether it is in the cache in the local cache according to the query conditions. If it is in the cache, it is directly taken from the cache and then returned to the user; otherwise, the data is read from the database, the query result is stored in the cache and returned to the user.

Insert picture description here

How long is the life cycle of the first level cache?

When MyBatis opens a database session, it will create a new SqlSession object. There will be a new Executor object in the SqlSession object, and the Executor object will hold a new PerpetualCache object; when the session ends, the SqlSession object and its internal Executor The object and the PerpetualCache object are also released.
If SqlSession calls the close() method, the first-level cache PerpetualCache object will be released, and the first-level cache will be unavailable;
if SqlSession calls clearCache(), the data in the PerpetualCache object will be cleared, but the object can still be used; in
SqlSession Perform any update operation (update(), delete(), insert()), the data of the PerpetualCache object will be cleared, but the object can continue to be used;
SqlSession first level cache workflow:

For a query, according statementId, params, rowBounds to construct a key value, the key value according to the result of the cache buffer corresponding to the Cache extracted key value stored in
judgment taken from the Cache specific key value according to whether the data empty, that is, whether the hit;
if hit, then return directly to the cached results;
If you do not hit:
to query data from the database to obtain a query result;
the key to query and results were used as key, value stored in the cache;
Return the query results;
  next, let's verify whether MyBatis's first level cache can only be shared in one session, and what problems will arise when operating the same data across sessions (different sessions). Judge whether it hits the cache: If you send the SQL to the database for execution again, it means that the cache is not hit; if you print the object directly, it means that you get the result from the memory cache.

1. Sharing in the same session (different sessions cannot be shared)

//Same as Session

SqlSession session1 = sqlSessionFactory.openSession();
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println(mapper1.selectBlogById(1002));
System.out.println(mapper1.selectBlogById(1002));

After executing the above sql, we can see that the console prints the following information (mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl needs to be configured), and we will find that our two queries are sent once to query the database Operation, this shows that the cache is working:

Insert picture description here

PS: The first level cache is stored in the query()——queryFromDatabase() of BaseExecutor. Get() before queryFromDatabase().

Copy code

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    。。。。。。try {
    
    
                ++this.queryStack;//从缓存中获取
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
    
    
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
    
    //缓存中获取不到,查询数据库
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
    。。。。。。
    }

Copy code
2. In the same session, update (including delete) will cause the first level cache to be emptied

Copy code

//同Session
SqlSession session1 = sqlSessionFactory.openSession();
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println(mapper1.selectBlogById(1002));
Blog blog3 = new Blog();
blog3.setBid(1002);
blog3.setName("mybatis缓存机制修改");
mapper1.updateBlog(blog3);
session1.commit();// 注意要提交事务,否则不会清除缓存
System.out.println(mapper1.selectBlogById(1002));

Copy code   The
  first level cache is cleared (unconditionally) by calling clearLocalCache() in the update() method in BaseExecutor, and it will be judged in the query.

Copy code

public int update(MappedStatement ms, Object parameter) throws SQLException {
    
    
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (this.closed) {
    
    
            throw new ExecutorException("Executor was closed.");
        } else {
    
    
        //清除本地缓存
            this.clearLocalCache();
            return this.doUpdate(ms, parameter);
        }
}

Copy code
3. Other sessions update data, resulting in dirty data being read (the first level cache cannot be shared across sessions)

Copy code

SqlSession session1 = sqlSessionFactory.openSession();
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
SqlSession session2 = sqlSessionFactory.openSession();
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
System.out.println(mapper2.selectBlogById(1002));
Blog blog3 = new Blog();
blog3.setBid(1002);
blog3.setName("mybatis缓存机制1");
mapper1.updateBlog(blog3);
session1.commit();
System.out.println(mapper2.selectBlogById(1002));

Copy code
Insufficiency of the first level cache:

When using the first level cache, because the cache cannot be shared across sessions, different sessions may have different caches for the same data. In a multi-session or distributed environment, there will be a problem of dirty data. If you want to solve this problem, you must use the second-level cache. MyBatis first level cache (MyBaits calls it Local Cache) cannot be turned off, but there are two levels to choose from:

Session level cache, in the same sqlSession, the same query will no longer query the database, directly from the cache.
Statement level cache to avoid pits: In order to avoid this problem, the level of the first level cache can be set to the statement level, so that the level one cache will be cleared after each query.
Second-level cache: The
  second-level cache is used to solve the problem that the first-level cache cannot be shared across sessions. The scope is at the namespace level and can be shared by multiple SqlSessions (as long as it is the same method in the same interface, it can be shared), The life cycle is synchronized with the application. If your MyBatis uses the second-level cache, and your Mapper and select statements are also configured to use the second-level cache, then when executing a select query, MyBatis will first take input from the second-level cache, and then the first-level cache , That is, the order of MyBatis query data is: Level 2 cache —> Level 1 cache —> Database.

As a wider range of cache, it must be in the outer layer of SqlSession, otherwise it cannot be shared by multiple SqlSessions. The first level cache is inside the SqlSession, so the first problem must be working before the first level cache, that is, only go to a session to get the first level cache when the second level cache is not available. The second question is, in which object should the secondary cache be maintained? To share across sessions, SqlSession itself and the BaseExecutor in it can no longer meet the needs, so we should create an object outside of BaseExecutor.

In fact, MyBatis uses a decorator class to maintain, that is, CachingExecutor. If the second level cache is enabled, MyBatis will decorate the Executor when creating the Executor object. CachingExecutor will determine whether the second-level cache has a cached result for the query request. If there is, it will return directly. If it is not delegated to the real queryer Executor implementation class, such as SimpleExecutor to execute the query, it will go to the first-level cache process. Finally, the result will be cached and returned to the user.

Insert picture description here

How to turn on the secondary cache

Step 1: Configure mybatis.configuration.cache-enabled=true, as long as cacheEnabled=false is not explicitly set, the basic actuator will be decorated with CachingExecutor.

Step 2: Configure tags in Mapper.xml:

<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
    size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>

That's basically it. 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 mapped statement file will refresh the cache.
The cache will use the least recently used algorithm (LRU, Least Recently Used) algorithm to clear unnecessary cache.
The cache is not refreshed periodically (that is, there is no refresh interval).
The cache will hold 1024 references to lists or objects (regardless of which query method returns).
The cache will be treated as a read/write cache, 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.
This more advanced configuration creates a FIFO buffer that is refreshed every 60 seconds. It can store up to 512 references to the result object or list, and the returned objects are considered read-only, so modifying them may occur in different threads. The caller in conflicts. The available removal strategies are:

LRU-Least Recently Used: 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 References: Remove objects more aggressively based on garbage collector status and weak reference rules.
The default removal strategy is LRU.

The flushInterval (refresh interval) attribute can be set to any positive integer, and the value set should be a reasonable amount of time in milliseconds. The default is not set, that is, there is no refresh interval, and the cache will only be refreshed when the statement is called.

The size (number of references) attribute can be set to any positive integer. Pay attention to the size of the object to be cached and the memory resources available in the operating environment. The default value is 1024.

The readOnly (read-only) attribute can be set to true or false. A read-only cache will return the same instance of the cached object to all callers. Therefore these objects cannot be modified. This provides a considerable performance improvement. The readable and writable cache will return (through serialization) a copy of the cached object. The speed will be slower, but more secure, so the default value is false.

Note: The second level cache is transactional. This means that when the SqlSession is completed and committed, or is completed and rolled back, but the insert/delete/update statement with flushCache=true is not executed, the cache will be updated.

After Mapper.xml is configured, select() will be cached. update(), delete(), insert() will refresh the cache. : If cacheEnabled=true, there is no configuration tag in Mapper.xml, is there a second-level cache? (No) Will CachingExecutor package objects still appear? (meeting)

As long as cacheEnabled=true the basic executor will be decorated. Whether it is configured or not determines whether the Cache object of this mapper will be created at startup, but it will eventually affect the judgment in the CachingExecutorquery method. What if some query methods require high real-time data and do not require secondary cache? We can explicitly turn off the second-level cache on a single Statement ID (default is true):

<select id="selectBlog" resultMap="BaseResultMap" useCache="false">
  二级缓存验证(验证二级缓存需要先开启二级缓存)

1. The transaction is not committed and the secondary cache does not exist

System.out.println(mapper1.selectBlogById(1002));
// 事务不提交的情况下,二级缓存不会写入
// session1.commit();
System.out.println(mapper2.selectBlogById(1002));

Why the transaction is not committed and the secondary cache is not effective? Because the second-level cache is managed by TransactionalCacheManager (TCM), and finally the getObject(), putObject and commit() methods of TransactionalCache are called, TransactionalCache holds real Cache objects, such as PerpetualCache that has been decorated with layers. In putObject, it is only added to entriesToAddOnCommit, and flushPendingEntries() is called to actually write to the cache only when its commit() method is called. It is called when DefaultSqlSession calls commit().

2. Use different sessions and mappers to verify that the second-level cache can cancel the above commit() comment across sessions

3. Perform addition, deletion and modification operations in other sessions, and the verification cache will be refreshed

Copy code

System.out.println(mapper1.selectBlogById(1002));
//主键自增返回测试
Blog blog3 = new Blog();
blog3.setBid(1002);
blog3.setName("mybatis缓存机制");
mapper1.updateBlog(blog3);
session1.commit();
System.out.println(mapper2.selectBlogById(1002));

Copy code
  Why add, delete and modify operations will clear the cache? FlushCacheIfRequired(ms) is called in the update() method of CachingExecutor, isFlushCacheRequired is the value of flushCache from the channel in the tag. The flushCache attribute of the addition, deletion, and modification operation is true by default.

When is the second level cache enabled?

The first level cache is enabled by default, and the second level cache needs to be configured to enable it. Then we must think about a question, under what circumstances is it necessary to turn on the secondary cache?

Because all additions, deletions and changes will refresh the second-level cache and cause the second-level cache to become invalid, it is suitable for use in query-oriented applications, such as historical transactions and historical orders. Otherwise, the cache loses its meaning.
If there are operations for the same table in multiple namespaces, such as the Blog table, if the cache is refreshed in one namespace but not refreshed in another namespace, dirty data will be read. Therefore, it is recommended to use only a single table in a Mapper.
  If you want multiple namespaces to share a second-level cache, what should you do? The problem of cross-namespace cache sharing can be solved by:

<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />

Cache-ref represents the Cache configuration that refers to other namespaces. The operations of the two namespaces use the same Cache. It can be used when there are few associated tables or when tables can be grouped according to business.

Note: In this case, multiple Mapper operations will cause the cache to refresh, and the cache is of little significance.

Third-party cache as secondary cache

In addition to the secondary cache that comes with MyBatis, we can also customize the secondary cache by implementing the Cache interface. MyBatis officially provides some third-party cache integration methods, such as ehcache and redis: https://github.com/mybatis/redis-cache, not too much introduction here. Of course, we can also use an independent cache service instead of the secondary cache that comes with MyBatis.

Custom cache:

In addition to the above-mentioned custom caching method, you can also completely override the caching behavior by implementing your own caching or creating adapters for other third-party caching schemes.

<cache type="com.domain.something.MyCustomCache"/>

This example shows how to use a custom cache implementation. The class specified by the type attribute must implement the org.mybatis.cache.Cache interface and provide a constructor that accepts a String parameter as an id. This interface is one of many complex interfaces in the MyBatis framework, but its behavior is very simple.

Copy code

public interface Cache {
    
    
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

Copy the code in
  order to configure your cache, simply add the public JavaBean attribute to your cache implementation, and then pass the attribute value through the cache element. For example, the following example will call a cache implementation named The method of setCacheFile(String file):

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

You can use all simple types as the types of JavaBean properties, and MyBatis will convert them. You can also use placeholders (such as ${cache.file}) to replace the values ​​defined in the configuration file properties. Starting from version 3.4.2, MyBatis already supports calling an initialization method after all properties are set. If you want to use this feature, please implement the org.apache.ibatis.builder.InitializingObject interface in your custom cache class.

public interface InitializingObject {
    
    
  void initialize() throws Exception;
}

Please note that the cache configuration and cache instance will be 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. This can be achieved by using two simple attributes on each statement. By default, the statement will be configured like this:

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

Given that this is the default behavior, obviously you should never explicitly configure a statement in this way. But if you want to change the default behavior, you only need to set the flushCache and useCache properties. For example, in some cases you may wish to exclude the result of a specific select statement from the cache, or you may wish to clear the cache for a select statement. Similarly, you may want to not flush the cache when certain update statements are executed.

									转载于博客园吴振照

Guess you like

Origin blog.csdn.net/mmmmmlj/article/details/108760990