preface

MyBatis cache introduction

The MyBatis cache is a cache that can be cached by a user.

MyBatis supports declarative data Caching. When an SQL statement is marked “cacheable,” all the data fetched from the database when it is first executed is stored in a cache, and the results are read from the cache when the statement is executed later, rather than hitting the database again. MyBatis provides a default implementation of caching based on Java HashMap and a default connector for connecting to OSCache, Ehcache, Hazelcast, and Memcached. MyBatis also provides apis for other caching implementations. MyBatis summary knowledge map to share with you.

MyBatis (MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis

This is also known as MyBatis level cache, level cache scope is SqlSession.

MyBatis also provides a global scope cache, also known as level 2 cache, also known as global cache.

Level 1 cache

test

Run the same query twice in the same session:

@Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); } finally { sqlSession.close(); }}Copy the code

MyBatis only performs 1 database query:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
Copy the code

Perform two different queries in the same session:

@Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 2); log.debug(user2); } finally { sqlSession.close(); }}Copy the code

MyBatis performs two database queries:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 2(Integer)
<==      Total: 1
User{id=2, name='FFF', age=50, birthday=Sat Dec 06 17:12:01 CST 2014}
Copy the code

Different sessions perform the same query:

@Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession2.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); } finally { sqlSession.close(); sqlSession2.close(); }}Copy the code

MyBatis conducted two database queries:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
Copy the code

The same session, update the data after query, query the same statement again:

@Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); user.setAge(100); sqlSession.update("org.format.mybatis.cache.UserMapper.update", user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); sqlSession.commit(); } finally { sqlSession.close(); }}Copy the code

The cache is cleared after the update operation:

==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: update USERS SET NAME = ? , AGE = ? , BIRTHDAY = ? where ID = ? ==> Parameters: Format (String), 23(Integer), 2014-10-12 23:20:13.0(Timestamp), 1(Integer) <== Updates: 1 ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}Copy the code

Obviously, the results validate the concept of level 1 caching. In the same SqlSession, the same SQL statement is cached, but the cache is cleared as soon as a new or update or delete operation is performed

Source code analysis

Before analyzing MyBatis level 1 cache, let’s take a brief look at some important classes and interfaces in MyBatis:

Org. Apache. Ibatis. Session. The Configuration class: MyBatis class global Configuration information

Org. Apache. Ibatis. Session. SqlSessionFactory interface: operating SqlSession factory interface, specific implementation class is DefaultSqlSessionFactory

Org. Apache. Ibatis. Session. SqlSession interface: execute SQL, management interface, specific implementation class is DefaultSqlSession

Org. Apache. Ibatis. Executor. Executor interface: SQL executor, SqlSession SQL execution is ultimately implemented through this interface. Common implementation classes are SimpleExecutor and CachingExecutor, which use the Decorator design pattern

Select * from SqlSession; select * from SqlSession;

DefaultSqlSession (SqlSession interface implementation class, MyBatis default use this class) selectList source code (our example is using the selectOne method, calling the selectOne method will eventually execute the selectList method) :

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code

We see that SqlSession ends up calling the Executor interface methods.

Let’s take a look at which implementation class the Executor interface property in DefaultSqlSession is.

DefaultSqlSession constructor (DefaultSqlSessionFactory)

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType, autoCommit); return new DefaultSqlSession(configuration, executor); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code

If DefaultSqlSessionFactory constructs DefaultSqlSession, the Executor interface implementation class is constructed by Configuration:

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
Copy the code

Executors are created depending on the ExecutorType. The most common is SimpleExecutor, and the example in this article also creates the implementation class. Finally, we see that if cacheEnabled is true, executors are wrapped with a layer decorator called CachingExecutor. The cacheEnabled attribute is the value of the cacheEnabled child of the Settings node in the Mybatis general configuration file. The default value is true, which means that cacheEnabled is enabled by default if you do not specify it in the MyBatis general configuration file.

Now, the only question left is, what does CachingExecutor actually do when it executes SQL?

With this question in mind, let’s go ahead (CachingExecutor’s Query method) :

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache ! = null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); if (! dirty) { cache.getReadWriteLock().readLock().lock(); try { @SuppressWarnings("unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList ! = null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } } List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks return list; } } return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code

Cache Cache = ms.getcache (); In this code, the cache is actually a level 2 cache. Since level 2 cache is not enabled (the contents of level 2 cache will be analyzed below), the last sentence is executed here. Here the delegate is SimpleExecutor SimpleExecutor didn’t Override the parent class of query method, thus eventually performed SimpleExecutor parent BaseExecutor query method.

So the most important code for level 1 caching is the BaseExecutor query method!

The BaseExecutor property localCache is an instance of the PerpetualCache type, which is one of the implementations of MyBatis’ Cache Cache interface. There is an internal property of type Map<Object, Object> used to store cached data. The localCache type is written dead inside BaseExecutor. LocalCache is a level 1 cache!

Let’s look at the problem of how a new or update or delete operation can clear the level 1 cache.

First of all, when MyBatis processes the addition or deletion, it will call the update method eventually, that is to say, the addition or deletion operation is an update operation in MyBatis eyes.

Let’s look at the update method for DefaultSqlSession:

public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code

Obviously, the Update method of CachingExecutor is called:

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
}
Copy the code

The flushCacheIfRequired method here flushes the second-level cache, which we’ll examine later. CachingExecutor is delegated (as previously analyzed) to the SimpleExecutor update method. SimpleExecutor does not have the Override BaseExecutor update method. So let’s look at the BaseExecutor update method:

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

We see a key line: clearLocalCache(); Check it out:

public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
}
Copy the code

Sqlsession is not closed. If sqlSession is added, deleted, or modified, it will clear level 1 cache.

The second level cache

The scope of the level 2 cache is global; in other words, the level 2 cache is no longer under SqlSession control.

Before testing the second-level cache, let me conclude:

The level 2 cache is global in scope and does not take effect until SqlSession is closed or committed.

Before we look at level 2 caching in MyBatis, let’s take a quick look at a class for level 2 caching in MyBatis (other related classes and interfaces have been examined before) :

configuration

Level 2 cache is different from Level 1 cache. Level 1 cache does not require configuration and is enabled by default. Level 2 caching requires some configuration.

This article describes the simplest configuration, which can be added to a mapper file:

<cache/>
Copy the code

Level 2 caching is related to three configurations:

1. The cacheEnabled in the setting of mybatis global configuration file should be true(default: true).

2. A node needs to be added to the mapper configuration file

3. Mapper configuration file select node need to add attribute useCache need to be true(default is true, do not set)

test

Commit SqlSession after the first query

@Test public void testCache2() { SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { String sql = "org.format.mybatis.cache.UserMapper.getById"; User user = (User)sqlSession.selectOne(sql, 1); log.debug(user); // Note that it must be submitted here. SqlSession.com MIT (); User user2 = (User)sqlSession2.selectOne(sql, 1); log.debug(user2); } finally { sqlSession.close(); sqlSession2.close(); }}Copy the code
**MyBatis; Preparing: select * from USERS WHERE ID =? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}Copy the code

SQL > select * from SqlSession; close SqlSession;

@Test public void testCache2() { SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { String sql = "org.format.mybatis.cache.UserMapper.getById"; User user = (User)sqlSession.selectOne(sql, 1); log.debug(user); sqlSession.close(); User user2 = (User)sqlSession2.selectOne(sql, 1); log.debug(user2); } finally { sqlSession2.close(); }}Copy the code

MyBatis only performed one database query:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
Copy the code

Query the same statement for different SqlSesson. SqlSession does not commit after the first query:

@Test public void testCache2() { SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { String sql = "org.format.mybatis.cache.UserMapper.getById"; User user = (User)sqlSession.selectOne(sql, 1); log.debug(user); User user2 = (User)sqlSession2.selectOne(sql, 1); log.debug(user2); } finally { sqlSession.close(); sqlSession2.close(); }}Copy the code

MyBatis performs two database queries:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
Copy the code

Source code analysis

MyBatis SQL parsing please refer to another blog MyBatis parsing dynamic SQL principle analysis. Let’s look at the parse of this cache:

XMLMappedBuilder (a parser class that parses every Mapper configuration file. Each Mapper configuration instantiates an XMLMapperBuilder class)

private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); }}Copy the code

We see the code that parses the cache:

private void cacheElement(XNode context) throws Exception { if (context ! = null) { String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = ! context.getBooleanAttribute("readOnly", false); Properties props = context.getChildrenAsProperties(); builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); }}Copy the code

After parsing the cache tag, the builderAssistant userNewCache method is used. BuilderAssistant is a help class of type MapperBuilderAssistant. The MapperBuilderAssistant class has a currentCache property of type Cache, which is the value represented by the Cache node in the mapper configuration file:

public Cache useNewCache(Class<? extends Cache> typeClass,
  Class<? extends Cache> evictionClass,
  Long flushInterval,
  Integer size,
  boolean readWrite,
  Properties props) {
    typeClass = valueOrDefault(typeClass, PerpetualCache.class);
    evictionClass = valueOrDefault(evictionClass, LruCache.class);
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(typeClass)
        .addDecorator(evictionClass)
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}
Copy the code

Ok, now the cache node in the Mapper configuration file is resolved to the currentCache value in the builderAssistant property in the XMLMapperBuilder instance.

Next, XMLMapperBuilder parses the SELECT node, which is parsed using XMLStatementBuilder (as well as other INSERT, update, and DELETE nodes) :

public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (! databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<? > resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", ! isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }Copy the code

This code has been parsing the attributes of some tags, and we see in the last line that we add the MappedStatement using the builderAssistant, The builderAssistant attribute is passed in with XMLMappedBuilder when the XMLStatementBuilder is constructed. Let’s continue with the builderAssistant addMappedStatement method:

Enter the setStatementCache:

private void setStatementCache( boolean isSelect, boolean flushCache, boolean useCache, Cache cache, MappedStatement.Builder statementBuilder) { flushCache = valueOrDefault(flushCache, ! isSelect); useCache = valueOrDefault(useCache, isSelect); statementBuilder.flushCacheRequired(flushCache); statementBuilder.useCache(useCache); statementBuilder.cache(cache); }Copy the code

Finally, the mapper configuration file is set in the builderAssistant property of the XMLMapperBuilder, which uses the XMLStatementBuilder to traverse the CRUD nodes, When traversing the CRUD nodes, the cache is set to the CRUD nodes. This cache is called level 2 cache.

Let’s go back to the source of the query, the CachingExecutor query method:

Enter TransactionalCacheManager putObject method:

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


private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
}
Copy the code

TransactionalCache’s putObject method:

public void putObject(Object key, Object object) {
    entriesToRemoveOnCommit.remove(key);
    entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
}
Copy the code

We see that the data is added to entriesToAddOnCommit, which is what entriesToAddOnCommit is, a Map property of TransactionalCache:

private Map<Object, AddEntry> entriesToAddOnCommit;
Copy the code

AddEntry is a class inside TransactionalCache:

private static class AddEntry { private Cache cache; private Object key; private Object value; public AddEntry(Cache cache, Object key, Object value) { this.cache = cache; this.key = key; this.value = value; } public void commit() { cache.putObject(key, value); }}Copy the code

Ok, now we find that with level 2 cache: to query for data, first fetch data from level 2 cache, if not, fetch data from level 1 cache, and then query the database if level 1 cache is not available either. The data is then thrown into the TransactionalCache object’s entriesToAddOnCommit property.

Let’s verify why level 2 caching takes effect only after SqlSession COMMIT or close.

Commit method for DefaultSqlSession

public void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code

CachingExecutor commit method:

public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
    dirty = false;
}
Copy the code

Tcm.com MIT commit TransactionalCacheManager method:

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

TransactionalCache commit method:

public void commit() { delegate.getReadWriteLock().writeLock().lock(); try { if (clearOnCommit) { delegate.clear(); } else { for (RemoveEntry entry : entriesToRemoveOnCommit.values()) { entry.commit(); } } for (AddEntry entry : entriesToAddOnCommit.values()) { entry.commit(); } reset(); } finally { delegate.getReadWriteLock().writeLock().unlock(); }}Copy the code

The AddEntry commit method was called:

public void commit() {
  cache.putObject(key, value);
}
Copy the code

Discovered! The commit method of AddEntry will throw data into the cache, that is, into the secondary cache!

The second level cache takes effect only after the close method is called because the commit method is called inside the close method. This article is not specific. Readers interested in the source code will see why.

other

Introduction to Cache Interfaces

Org. Apache. Ibatis. Cache. The cache is a cache interface MyBatis, want to implement a custom cache need to implement this interface.

The implementation class for the Cache interface in MyBatis also uses decorator design patterns.

Let’s take a look at some of its implementation classes:

Brief description:

LRU – Least recently used: Removes the object that has not been used for the longest time.

FIFO – First in, first out: Objects are removed in the order in which they enter the cache.

SOFT – SOFT reference: Removes objects based on garbage collector status and SOFT reference rules.

WEAK – WEAK references: Objects based on garbage collector state and WEAK reference rules are removed more aggressively.

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
Copy the code

The eviction property of cache nodes can be set, as well as other properties.

Cache – ref node

A cache-ref node can also be added to the mapper configuration file, which has a namespace attribute.

If every Mapper file is cache-ref and namespace is the same, it represents a truly global cache.

If only the cache node is used, it means that the query inside the mapper is cached. Other mapper files are not used. This is not a global cache.

conclusion

In general, MyBatis source code looks relatively easy, this article from practice and source in-depth analysis of MyBatis cache principle, hope to be helpful to readers.

The last

My side organized a MyBatis information document MyBatis, Spring series of family, Java systematic information (including Java core knowledge points, interview topics and 20 years of the latest Internet real questions, e-books, etc.) need friends can pay attention to the public number can be obtained.