Talk about MyBatis caching mechanism

Level 1 cache experiment

Next, through experiments, understand the effect of MyBatis first-level cache, and restore the modified data after each unit test.

The first is to create the sample table student, create the corresponding POJO class and the method of addition and modification, 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;

In the following experiment, the student with id 1 is named Karen.

Experiment 1

Open the first-level cache, the scope is session level, call three times getStudentById, the code is 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));  }

Results of the:

We can see that only the first time the database is actually queried, subsequent queries use the first-level cache.

experiment 2

The modification operation to the database has been added to verify whether the first-level cache will be invalidated if the modification operation occurs to the database in a database session.

@Testpublic void addStudent() throws Exception {
   
       SqlSession sqlSession = factory.openSession(true); // 自动提交事务    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);    System.out.println(studentMapper.getStudentById(1));    System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "个学生");    System.out.println(studentMapper.getStudentById(1));    sqlSession.close();}

Results of the:

We can see that the same query executed after the modify operation queries the database, and the first-level cache is invalidated .

experiment 3

Turn on two SqlSession, sqlSession1query data in , enable the first-level cache to take effect, sqlSession2update the database in , and verify that the first-level cache is only shared within the database session.

@Testpublic 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.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));    System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));    System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "个学生的数据");    System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));    System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));}

sqlSession2Updated the name of the student with id 1, from Karen to Xiaocen, but in the query after session1, the name of the student with id 1 is still Karen, and dirty data appeared, which also proved the previous assumption, level one The cache is only shared within a database session.

Level 1 cache workflow & source code analysis

So, what is the workflow of the first level cache? Let's learn from the source code level.

work process

The timing diagram of the first-level cache execution is shown in the figure below.

Source code analysis

Next, I will read the source code of the core classes related to MyBatis query and the first-level cache. This is also helpful for learning the second-level cache later.

SqlSession : Provides all the methods needed for interaction between the user and the database, hiding the underlying details. The default implementation class is DefaultSqlSession.

Executor : SqlSessionProvide users with methods to operate the database, but the responsibilities related to database operations will be delegated to Executor.

As shown in the figure below, Executor has several implementation classes, which endow Executor with different capabilities. You can learn the basic functions of each class by yourself according to the class name.

In the source code analysis of the first-level cache, BaseExecutorthe internal implementation of the main study.

BaseExecutor : BaseExecutorIt is an abstract class that implements the Executor interface, defines several abstract methods, and delegates specific operations to subclasses for execution during execution.

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;

In the introduction to the first-level cache, it is mentioned that Local Cachethe query and write of the pair are Executordone internally. After reading BaseExecutorthe code, it is found Local Cachethat it is BaseExecutoran internal member variable, as shown in the following code.

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

Cache : The Cache interface in MyBatis provides the most basic operations related to caching, as shown in the following figure:

There are several implementation classes, which are assembled with each other using the decorator pattern, providing a wealth of ability to manipulate the cache. Some of the implementation classes are shown in the following figure:

BaseExecutorOne of the member variables PerpetualCacheis the most basic implementation of the Cache interface. Its implementation is very simple. It holds a HashMap inside, and the operation on the first-level cache is actually the operation on the HashMap. As shown in the following code:

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

After reading the relevant core class codes, the relevant codes involved in the first-level cache work are considered from the source code level. For the sake of space, the source codes are appropriately deleted. Readers and friends can combine this article for more detailed study later.

In order to perform interaction with the database, it first needs to be initialized SqlSessionby DefaultSqlSessionFactoryenabling SqlSession:

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

At the time of initialization SqlSesion, a new class will be used to Configurationcreate a new one Executoras DefaultSqlSessiona parameter of the constructor. The code to create an Executor is 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);  }  // 尤其可以注意这里,如果二级缓存开关开启的话,是使用CahingExecutor装饰BaseExecutor的子类  if (cacheEnabled) {
   
      executor = new CachingExecutor(executor);  }  executor = (Executor) interceptorChain.pluginAll(executor);  return executor;}

SqlSessionAfter creation, according to the different types of Statment, it will enter SqlSessiondifferent methods. If it is Selecta statement, it will be executed at the end SqlSession. selectListThe code is as follows:

@Overridepublic <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);}

SqlSessionThe specific query responsibility is delegated to Executor. If only the first-level cache is enabled, the method will be entered BaseExecutorfirst query. The code looks like this:

@Overridepublic <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);}

Guess you like

Origin blog.csdn.net/m0_69804655/article/details/130321077