preface

MyBatis is a common Java database access layer framework. In daily work, most of the developers use the default cache configuration of MyBatis, but the MyBatis cache mechanism has some shortcomings, and it is easy to cause dirty data in use, forming some potential dangers. I have also dealt with some development problems caused by MyBatis cache in business development. With personal interest, I hope to sort out MyBatis cache mechanism for readers from the perspective of application and source code. The code and database tables involved in this analysis are located on GitHub at mybatis-cache-demo.

directory

This article unfolds in the following order.

  • This section describes level 1 cache and related configurations.
  • Level 1 cache workflow and source code analysis.
  • Level 1 cache summary.
  • This section describes level 2 cache and related configurations.
  • Secondary cache source code analysis.
  • Level 2 cache summary.
  • Full text summary.

Level 1 cache

Level 1 Cache introduction

In the application running process, it is possible to execute SQL with identical query conditions for many times in a database session. MyBatis provides the solution of level 1 cache optimization for this part of the scenario. If the SQL statement is the same, level 1 cache will be preferentially hit, avoiding direct database query and improving performance. The following figure shows the specific execution process.



Each SqlSession holds an Executor, and each Executor has a LocalCache. When a user initiates a query, MyBatis generates a MappedStatement based on the currently executed statement and queries the MappedStatement in the Local Cache. If the Cache is hit, MyBatis returns the result to the user directly. If the Cache is not hit, MyBatis queries the database and writes the result to the Local Cache. The result is returned to the user. The class diagram of the concrete implementation class is shown below.

Level 1 Cache Configuration

Let’s see how to use MyBatis level 1 cache. Developers can use level 1 cache by adding the following statement in the MyBatis configuration file. There are two options: SESSION or STATEMENT. The default is SESSION level. All statements executed in a MyBatis SESSION share the same cache. One is the STATEMENT level, which means that the cache is only valid for the STATEMENT currently executed.

<setting name="localCacheScope" value="SESSION"/>
Copy the code

Level 1 cache experiment

Next, through experiments, to understand the effect of MyBatis level 1 cache, after each unit test, please restore the modified data. The first step is to create the sample table Student, create the corresponding POJO class and increment methods, which can be viewed in the Entity package and mapper package.

CREATE TABLE `student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
  `age` tinyint(3) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Copy the code

In the following experiment, student id 1 is Named Karen.

Experiment 1

Enable level 1 caching at session level and call getStudentById three times as follows:

public void getStudentById() throws Exception { SqlSession sqlSession = factory.openSession(true); StudentMapper StudentMapper = sqlsession. getMapper(studentmapper. class); System.out.println(studentMapper.getStudentById(1)); System.out.println(studentMapper.getStudentById(1)); System.out.println(studentMapper.getStudentById(1)); }Copy the code

Execution Result:



As you can see, only the first actual query was made to the database, and subsequent queries used level 1 caching.

Experiment 2

Added database modification operation to verify whether level 1 cache is invalid if a database modification operation occurs during a database session.

@Test public void addStudent() throws Exception { SqlSession sqlSession = factory.openSession(true); StudentMapper StudentMapper = sqlsession. getMapper(studentmapper. class); System.out.println(studentMapper.getStudentById(1)); System.out.println(" add "+ studentMapper.addStudent(buildStudent()) +" studentMapper.addStudent "); System.out.println(studentMapper.getStudentById(1)); sqlSession.close(); }Copy the code

Execution Result:



We can see that the same query, executed after the modification operation, queries the database,Level 1 cache invalidation.

Experiment 3

Open two SQLsessions, query data in sqlSession1 to make level 1 cache effective, update database in sqlSession2 to verify level 1 cache is only shared within database sessions.

@Test public void testLocalCacheScope() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); System. The out. Println (" studentMapper2 updated "+ studentMapper2. UpdateStudentName (" little cen", 1) + "a student data"); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentById (1)); }Copy the code



Mysql > update student id = 1; mysql > update student id = 1; mysql > update student id = 1; mysql > update student id = 1;

Level 1 Cache workflow & source code analysis

So, what is the workflow of level 1 caching? Let’s take a look at the source code.

The working process

The sequence diagram of level 1 cache execution is shown below.

Source code analysis

Next we will walk through the source code of the MyBatis query related core classes and level 1 cache. This is also useful for learning about level 2 caching later.

SqlSession: externally provides all the methods needed to interact between the user and the database, hiding the low-level details. The default implementation class is DefaultSqlSession.

ExecutorSqlSession provides the user with the means to operate on the database, but the responsibility for operating on the database is delegated to Executor.

As shown in the figure below, Executor has several implementation classes that give Executor different capabilities. You can learn the basic functions of each class by class name.

In the source code analysis of level cache, we mainly study the internal implementation of BaseExecutor. BaseExecutor: BaseExecutor is an abstract class that implements the Executor interface. It defines abstract methods that delegate operations to subclasses.

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;
Copy the code

The introduction to level 1 caching mentioned that querying and writing to Local Cache is done inside Executor. Local Cache is a member variable inside BaseExecutor after reading the BaseExecutor code, as shown in the following code.

public abstract class BaseExecutor implements Executor {
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
Copy the code

CacheThe Cache interface in MyBatis provides the most basic operations related to Cache, as shown in the following figure.



There are several implementation classes that are assembled with each other using the decorator pattern to provide rich caching capabilities, some of which are shown in the figure below.



The PerpetualCache, one of the BaseExecutor member variables, is a fundamental implementation of the Cache interface. It is a very simple implementation that holds a HashMap internally and operates on a HashMap for a level 1 Cache. See the code below.

public class PerpetualCache implements Cache {
  private String id;
  private Map<Object, Object> cache = new HashMap<Object, Object>();
Copy the code

After reading the relevant core class code, from the source code level of the level cache work involved in the relevant code, for the sake of space, to do appropriate deletion of the source code, readers can combine this article, the subsequent study in more detail. SQL > initialize SqlSession with DefaultSqlSessionFactory

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    ............
    final Executor executor = configuration.newExecutor(tx, execType);     
    return new DefaultSqlSession(configuration, executor, autoCommit);
}
Copy the code

When SqlSesion is initialized, a new Executor is created using the Configuration class. As an argument to the DefaultSqlSession constructor, the Executor code is created as follows:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // In particular, if level 2 caching is enabled, Is a subclass of BaseExecutor decorated with CahingExecutor if (cacheEnabled) {executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }Copy the code

SqlSession selectList SqlSession selectList SqlSession selectList

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
Copy the code

SqlSession delegates specific query responsibility to the Executor. If only level 1 caching is enabled, the BaseExecutor query method is first entered. The code looks like this:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
Copy the code

In the code above, a CacheKey is generated based on the parameters passed in. Enter the method to see how the CacheKey is generated, as follows:

CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); Cachekey.update (value);Copy the code

In the above code, we pass in the CacheKey class the Id of the MappedStatement, the OFFSET of the SQL, the limit of the SQL, the SQL itself, and the parameters in the SQL to make up the CacheKey. Here is the internal structure of this class:

private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;

private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;

public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLYER;
    this.count = 0;
    this.updateList = new ArrayList<Object>();
}
Copy the code

The first is the member variable and constructor, which has an initial Hachcode and multiplier, and maintains an internal Updatelist. In the CacheKey update method, a hashCode and checksum calculation is performed and the parameters passed in are added to the Updatelist. See the code below.

public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
}
Copy the code

We also override the CacheKey’s equals method as follows:

@Override public boolean equals(Object object) { ............. for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (! ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; }Copy the code

Except for comparisons of hashcode, Checksum, and count, CacheKey equality is considered as long as the elements in updatelist are one-to-one equal. Two SQL statements are considered the same as long as the following five values are the same.

Statement Id + Offset + Limmit + Sql + Params

The BaseExecutor query method continues as follows:

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list ! = null) {// This is mainly used for stored procedures. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }Copy the code

If you can’t find it, look it up in the database, in queryFromDatabase, and write to localCache. At the end of the execution of the query method, the level 1 cache is determined to be STATEMENT level, and if so, the cache is cleared, which is why STATEMENT level 1 caches cannot share localCache. The code looks like this:

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache();
}
Copy the code

At the end of the source code analysis, we confirm why the cache is flushed if the INSERT/DELETE /update methods are used. SqlSession insert method and delete method, will uniformly follow the update process, the code is as follows:

@Override
public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
   @Override
  public int delete(String statement) {
    return update(statement, null);
}
Copy the code

The update method is also delegated to Executor. The BaseExecutor execution method is shown below.

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
}
Copy the code

The localCache is cleared before each update.

At this point, the level of cache workflow and source code analysis is completed.

conclusion

  1. MyBatis Level 1 cache has the same life cycle as SqlSession.
  2. The internal design of MyBatis level 1 cache is simple, just a HashMap with no capacity limit, which is lacking in the functionality of the cache.
  3. The maximum cache level of MyBatis is within SQLSessions. If there are multiple SQLsessions or a distributed environment, database write operations may cause dirty data. You are advised to set the cache level to Statement.

The second level cache

Introduction to Level 2 Cache

In the level 1 cache mentioned above, the maximum shared scope is within a SqlSession. If multiple SQLsessions need to share the cache, the level 2 cache needs to be used. After level-2 cache is enabled, CachingExecutor is used to decorate Executor. Before entering level-1 cache query process, CachingExecutor is used to query Level-2 cache. The workflow is as follows:

After level-2 Cache is enabled, all operations in a namespace affect the same Cache. That is, level-2 Cache is shared by multiple SQLSessions and is a global variable. When caching is enabled, the data query execution process is level 2 cache -> Level 1 cache -> database.

Level 2 Cache Configuration

To use level 2 cache correctly, complete the following configuration.

  1. Enable level 2 caching in MyBatis configuration file.
    <setting name="cacheEnabled" value="true"/>
    Copy the code
  2. Configure cache or cache-ref in MyBatis mapping XML.

The cache tag is used to declare that the namespace uses level 2 caching and can be customized.

<cache/>
Copy the code
  • Type: The type used by the cache. The default is PerpetualCache, as mentioned in level 1 caching.
  • Eviction: A design designed to define recycling strategies, FIFO, LRU, etc.
  • FlushInterval: flushs the cache automatically at a certain time, in milliseconds.
  • Size: indicates the maximum number of cached objects.
  • ReadOnly: Indicates whether it is read-only. If it is configured as read-write, the corresponding entity class must be serialized.
  • Blocking: If the corresponding key cannot be found in the cache, does the blocking continue until the corresponding data enters the cache?

Cache-ref indicates a cache configuration that references another namespace. Operations on both namespaces use the same cache.

<cache-ref namespace="mapper.StudentMapper"/>
Copy the code

Second level cache experiment

Next, we understand some characteristics of the use of MyBatis secondary cache through experiments. In this experiment, the student name with ID 1 is initialized as a dot.

Experiment 1

SqlSession1, sqlSession2, sqlSession1, sqlSession2

@Test public void testCacheWithoutCommitOrClose() throws Exception { SqlSession sqlSession1 = factory.openSession(true);  SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentById (1)); }Copy the code

Execution Result:



As you can see, level 2 caching does not work when sqlSession does not call commit().

Experiment 2

When a transaction is committed, the same query sqlSession2 will fetch data from the cache after sqlSession1 has queried the data.

@Test public void testCacheWithCommitOrClose() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); sqlSession1.commit(); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentById (1)); }Copy the code



As you can see from the graph, the sqlsession2 query uses a cache with a hit ratio of 0.5.

Experiment 3

Tests whether the update operation refreshes the level 2 cache under the namespace.

@Test public void testCacheWithUpdate() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); SqlSession sqlSession3 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentById (1)); sqlSession1.commit(); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentById (1)); StudentMapper3. UpdateStudentName (" all ", 1); sqlSession3.commit(); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentById (1)); }Copy the code



The StudentMapper namespace of SQlsession2 is not in the Cache, but in the database.

Experiment 4

Verify that MyBatis level 2 cache is not suitable for multiple table queries in mapping files. Usually, we will create a separate mapping file for each single table. Because the level 2 cache of MyBatis is namespace-based, the namspace where the multi-table query statement is located cannot sense the modification of the table involved in the multi-table query by the statements in other namespaces, resulting in dirty data problems.

@Test public void testCacheWithDiffererntNamespace() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); SqlSession sqlSession3 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class); System. The out. Println (" studentMapper read data: "+ studentMapper. GetStudentByIdWithClassInfo (1)); sqlSession1.close(); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentByIdWithClassInfo (1)); ClassMapper. UpdateClassName (" special class ", 1); sqlSession3.commit(); System. The out. Println (" studentMapper2 read data: "+ studentMapper2. GetStudentByIdWithClassInfo (1)); }Copy the code

Execution Result:



In this experiment, we introduced two new tables, one class and one classroom. The class saves the class ID and class name, and the classroom saves the class ID and student ID. We added a query methods in StudentMapper getStudentByIdWithClassInfo, used to query the student in the class, involves the multi-table query. Add updateClassName to ClassMapper to update the class name based on the class ID.

When the studentMapper of SQlsession1 queries data, the level-2 cache takes effect. It is saved in the cache of the namespace of StudentMapper. UpdateClassName does not belong to the namespace of StudentMapper when the class table is updated by sqlSession3’s updateClassName method. Therefore, the StudentMapper cache does not detect the change and does not flush the cache. When the same query is initiated again in StudentMapper, dirty data is read from the cache.

The experiment of five

To solve the problem in experiment 4, we can use the Cache ref and have the ClassMapper refer to the StudenMapper namespace, so that the Sql operations corresponding to the two mapping files use the same Cache.

Execution Result:



As a result, the cache becomes more granular, and all operations under multiple Mapper namespaces affect the cache usage.

Secondary cache source code analysis

The workflow of MyBatis level 2 cache is similar to the level 1 cache mentioned above, except that before level 1 cache processing, the Subclass of BaseExecutor is decorated with CachingExecutor, and the query and write functions of Level 2 cache are realized before delegating specific responsibilities to the delegate. The concrete class diagram is shown in the figure below.

Source code analysis

The source code analysis starts with the CachingExecutor Query method. The source code reading process involves too many knowledge points to be explained in detail. Readers can consult related materials to learn. The Query method of CachingExecutor first retrives the Cache assigned during configuration initialization from the MappedStatement.

Cache cache = ms.getCache();
Copy the code

Essentially the use of the decorator pattern, the specific decorator chain is

SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache

The following is an introduction to the specific Cache implementation classes, and their combinations give Cache different capabilities.

  • SynchronizedCache: Synchronizes the Cache, which is simple and uses the synchronized modification method.
  • LoggingCache: Log function, decoration class, used to record cache hit ratio. If DEBUG mode is enabled, hit ratio logs are generated.
  • SerializedCache: Serialization function that serializes values to the cache. This feature is used by the cache to return a Copy of an instance for thread-safe storage.
  • LruCache: The Cache implementation of the Lru algorithm is used to remove the least recently used key or value.
  • PerpetualCache: As a basic caching class, the underlying implementation is simple and uses HashMap directly.

The next step is to determine whether the cache needs to be flushed, as shown below:

flushCacheIfRequired(ms);
Copy the code

In default Settings SELECT statements do not flush the cache, insert/update/delte does. Enter the method. The code looks like this:

private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
}
Copy the code

MyBatis CachingExecutor hold TransactionalCacheManager, namely the above code in TCM. TransactionalCacheManager holds a Map in the code as shown below:

private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
Copy the code

This Map holds the mapping between the Cache and the Cache wrapped in TransactionalCache. TransactionalCache implements the Cache interface. CachingExecutor wraps the Cache by default. If a transaction is committed, operations on the Cache take effect, and if the transaction is rolled back or not committed, the Cache is not affected. In TransactionalCache clear, there are two sentences. Clear the list that needs to be added to the cache at commit time, and set the cache to be cleared at commit time, as shown below:

@Override
public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
}
Copy the code

CachingExecutor continues down the line, and ensureNoOutParams is primarily used to handle stored procedures.

if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, parameterObject, boundSql);
Copy the code

An attempt will then be made to retrieve the cached list from TCM.

List<E> list = (List<E>) tcm.getObject(cache, key);
Copy the code

In the getObject method, the responsibility for getting the value is passed all the way to the PerpetualCache. If the key is not found, it will be added to the Miss set, which is mainly for the statistical hit ratio.

Object object = delegate.getObject(key);
if (object == null) {
    entriesMissedInCache.add(key);
}
Copy the code

CachingExecutor continues down, and if it queries for data, it calls the tcm.putObject method to put the value into the cache.

if (list == null) {
    list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    tcm.putObject(cache, key, list); // issue #578 and #116
}
Copy the code

The TCM put method does not operate on the cache directly, but only puts the data and key into the Map to be committed.

@Override
public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
}
Copy the code

If you do not call commit, there is no direct impact on the level 2 cache because of TranscationalCache. So let’s look at what happens in the COMMIT method of Sqlsession. The code looks like this:

@Override
public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
Copy the code

Because we are using CachingExecutor, we will first enter the Commit method implemented by CachingExecutor.

@Override
public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
}
Copy the code

The wrapper Executor is delegated the specific commit responsibility. Tcm.com MIT (), TCM will eventually call TrancationalCache.

public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
}
Copy the code

ClearOnCommit: TrancationalCache: Clear; clearOnCommit: TrancationalCache: Clear; Specific cleanup responsibilities are delegated to the wrapped Cache class. Then enter the flushPendingEntries method. The code looks like this:

private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); }... }Copy the code

In flushPendingEntries, the Map to be submitted is looped and delegated to the wrapped Cache class for putObject. This process is repeated for subsequent query operations. If it is insert | update | the delete, will be unified into the CachingExecutor update method, which calls the function, the code is as follows:

private void flushCacheIfRequired(MappedStatement ms)
Copy the code

After the level 2 cache execution flow, it will enter the level 1 cache execution flow, so it will not be described here.

conclusion

  1. Compared with level 1 Cache, level 2 Cache of MyBatis realizes the sharing of Cache data between SqlSession. At the same time, the granularity is more fine, and it can reach namespace level. Different combinations of classes can be realized through Cache interface, and the controllability of Cache is stronger.
  2. In MyBatis multi-table query, there is a great possibility of dirty data and design defects, and the conditions for safe use of second-level cache are harsh.
  3. In distributed environment, as the default MyBatis Cache implementation is based on local, it is inevitable to read dirty data in distributed environment, so centralized Cache is needed to implement the Cache interface of MyBatis, which has certain development cost. Using distributed caches such as Redis,Memcached and so on directly may be cheaper and more secure.

The full text summary

This paper introduces the basic concept of MyBatis primary and secondary cache, and analyzes the caching mechanism of MyBatis from the perspective of application and source code. Finally, the MyBatis cache mechanism is summarized. I suggest that MyBatis cache feature should be closed in production environment, and it may be more suitable to use as an ORM framework.

Author’s brief introduction

Karen is a back-end R&D engineer of Meituan-Dianping. She graduated from Shanghai Maritime University in 2016 and is now engaged in the development of Meituan-Dianping catering platform. Public ID: KailunTalk, welcome to follow, discuss more technical knowledge together.

Recruitment information

We are looking for you to join us in Shanghai. We are looking for positions: Java background, data development, front end, QA, product, product operation, business analysis, etc. Internal resume email: weiyanping#meituan.com








If you find any mistakes or have any questions about the content, you can follow the wechat official account of Meituantech and leave a message to us in the background. Every week, we will select a warm-hearted friend and send a nice small gift. Scan the code to pay attention to us!