Deep understanding of mybatis L2 cache

Deep understanding of mybatis L2 cache

In our daily projects, we often don't choose to use MyBatis's second level cache, because when you are not familiar with the second level cache. Because improper use can easily cause dirty reads, let's take a look at the slot points and highlights of L2 cache.

First, we need to know what the L2 cache needs to do

Second level cache (application level cache, cross thread cache) is different from first level cache (session level cache, thread unsafe), so its hit rate is higher than first level cache

Among them, the second level cache has several core knowledge points:

1. Storage mode:

1) memory: This is the most commonly used way for us, which is simple and fast; the disadvantage is that it cannot be persistent and has limited capacity.

2) hard disk: although it can be persistent and has large capacity, the biggest drawback is that the access speed is slow! Generally, it is not used for caching.

3) third party integration (redis): the most popular way at present, with relatively large capacity, fast speed and durability. And redis can also support distributed systems

2. Elimination strategy:

1) FIFO first in first out, the bottom layer uses queue to realize

2) LRU is implemented with LinkedHashMap at least at the bottom recently

3) WeakReference: weak application. The cached objects are wrapped with weak application. When the JVM performs GC, this object will be cleared regardless of whether the current memory space is enough or not.

4) softrefresh: soft reference. Its basic principle is similar to weak application. The difference is that when GC is running out of memory, the cache object will be recycled.

3. Overdue cleaning: cleaning up data that has been stored for too long

4. Thread safety: ensure that the cache can be used by multiple threads at the same time

6. Serialization: About serialization, because the second level cache is the application level cache, it can be used by cross thread. When different threads get the same object and do different operations, different results will affect each other. The obtained objects are deserialized. The obtained object ID is different, but the object value is the same, so different operations will not interfere with each other.

When we look at the source code, we must first understand the design pattern. The most attractive design of the whole MyBatis, but less functions are used;

The main reason is. When using the second level cache, you need to use or @ CacheNameSpace to declare the cache space. This greatly improves the flexibility and brings the disadvantage of public cache space. In today's increasingly complex business logic and table splitting, multi table operation is very common! So the cache space becomes extremely complex and uncontrollable. Dirty data is very easy to read.

Design mode used: decorator + responsibility chain mode

Note: BlockingCache here needs to be turned on through the configuration property (@ CacheNamespace(blocking = true)), which is turned off by default.

Through the source code debug, you can see the link of the decorator.
The cached policy settings are annotated with @ CacheNamespace,
package org.apache.ibatis.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.impl.PerpetualCache;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CacheNamespace {
  // Specifies the storage implementation class of the cache. The default is to implement the storage through the HashMap
  Class<? extends org.apache.ibatis.cache.Cache> implementation() default PerpetualCache.class;
	
  // Elimination strategy default Lru
  Class<? extends org.apache.ibatis.cache.Cache> eviction() default LruCache.class;

  // The validity of cache is 0 by default
  long flushInterval() default 0;

  int size() default 1024;
	
  // Whether to serialize or deserialize
  boolean readWrite() default true;

  // Whether to prevent cache penetration
  boolean blocking() default false;

  /**
   * Property values for a implementation object.
   * @since 3.4.2
   */
  Property[] properties() default {};

}

Cache access flow of MyBatis query

On the hit conditions of L2 cache

Session commit, SQL statement and statement ID, RowBounds are the same.

About session commit, it's about the next cache structure. For the consideration of use here, put a picture first

On the structure of L2 cache

Let's first look at the properties of the cache executor

public class CachingExecutor implements Executor {
	// Decorator mode, pointing to the next actuator
  private final Executor delegate;
  // Transaction cache manager,
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
}

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

We can see from the source code that the decisive attribute is transactional cache manager, which provides the most basic PUT cache, GET cache, clear cache, commit and other operations.

This transaction cache manager plays a huge role when different sessions access it. This is also the proof of cross session caching

The figure below shows the structure well

About the execution process of L2 cache

The caching Executor is loaded into the Executor's execution chain through the decorator mode.

Query operation query:

When the session calls query(), it will form a cache Key based on query statements, parameters and other data, and then try to read data from the secondary cache (real-time acquisition). When it is read, it will return directly. If not, it will call the decorated Executor to query the database, and then fill in the corresponding temporary storage area.

Update operation update: (if flushCache=true is configured in the query, the update operation will be the same.)

When the update operation is executed, the cache key will also be composed based on the query statements and parameters, and then the cache will be cleared before the update operation. Here, the clear is only for the temporary storage area, and the clear flag is recorded, so that when the session is submitted, the second level cache space can be cleared according to the flag.

commit operation:

When the session performs the commit operation, the session commits the changes of the staging area to the secondary cache.

Here is the whole execution process

Tags: Session Apache Java Mybatis

Posted on Fri, 26 Jun 2020 00:49:48 -0400 by ryanbutler