编程知识 cdmana.com

Mybatis cache -- first level cache and source code analysis

What is caching ?

Caching is data stored in memory , And memory reads are very fast , Usually we put Update changes are less frequent And Frequent queries The data of , After the first query from the database , Stored in the cache , In this way, you can avoid multiple interactions with the database , So as to improve the response speed .

mybatis It also provides cache support , It is divided into :

  • First level cache
  • Second level cache

image-20201110221457670

  1. First level cache :
    Every sqlSeesion Objects have a first level cache , We need to construct sqlSeesion object , There is one in the object HashMap For storing cached data . Different sqlSession Cache data area between (HashMap) It doesn't affect each other .
  2. Second level cache :
    The L2 cache is mapper Level ( Also known as namespace Level ) The cache of , Multiple sqlSession To operate the same Mapper Of sql sentence , Multiple SqlSession Second level cache can be shared , Second level cache is cross sqlSession Of .

First level cache

First of all, let's open the first level cache , The first level cache is Default on Of , So it's very convenient for us to experience the first level cache .

Test one 、

Prepare a table , There are two fields id and username
image-20201110232032148

In the test class :

public class TestCache {
    private SqlSession sqlSession;
    private UserMapper mapper;
    @Before
    public void before() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = build.openSession();
        mapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void testFirst(){
        // First query ———— First of all, go to the first level cache to query 
        User user1 = mapper.findById(1);
        System.out.println("======"+user1);
		// Second query 
        User user2 = mapper.findById(1);
        System.out.println("======"+user2);
        
        System.out.println(user1==user2);
    }
}

We use the same sqlSession Respectively according to the id To query users ,id All for 1, Then compare their address values . Take a look at the results :

23:16:25,818 DEBUG findById:159 - ==>  Preparing: select * from user where id=? 
23:16:25,862 DEBUG findById:159 - ==> Parameters: 1(Integer)
23:16:25,894 DEBUG findById:159 - <==      Total: 1
======User{id=1, username='lucy'}
======User{id=1, username='lucy'}
true

We found that there was only one print SQL, At the same time, their address values are the same .

Explain the first query , Not in cache , And then query from the database —— perform SQL, And then it's stored in the cache , The second query found that there was , So just take it out of the cache , No more execution SQL 了 .
image-20201110233551326

We just mentioned , The data structure of L1 cache is a hashmap, That is to say, there are key Yes value.
value That's what we found out ,key It's made up of multiple values :

  • statementid :namespace.id form
  • params: The parameters passed in when querying
  • boundsql:mybatis The underlying object , It encapsulates what we're going to execute sql
  • rowbounds: Paging object
  • ... There are also some will explain in the source code analysis

Test two 、

Let's modify it now , After we checked the first result , Change the value of the database , And then make a second query , Let's take a look at the query results .id=1 Of username by lucy
image-20201110232032148

    @Test
    public void testFirst(){
        // First query 
        User user1 = mapper.findById(1);
        System.out.println("======"+user1);

        // modify id by 1 Of username
        User updateUser = new User();
        updateUser.setId(1);
        updateUser.setUsername(" Lee thought ");
        mapper.updateUser(updateUser);
        // Commit transactions manually 
        sqlSession.commit();

        // Second query 
        User user2 = mapper.findById(1);
        System.out.println("======"+user2);

        System.out.println(user1==user2);
    }

image-20201110235221440

Make a breakpoint where the transaction is committed , You can see that two of them are implemented sql, One is to inquire id by 1, One is modification id by 1 Of username

final result :

23:50:15,933 DEBUG findById:159 - ==>  Preparing: select * from user where id=? 
23:50:15,976 DEBUG findById:159 - ==> Parameters: 1(Integer)
23:50:16,002 DEBUG findById:159 - <==      Total: 1
======User{id=1, username='lucy', roleList=null, orderList=null}
23:50:16,003 DEBUG updateUser:159 - ==>  Preparing: update user set username=? where id =? 
23:50:16,005 DEBUG updateUser:159 - ==> Parameters:  Lee thought (String), 1(Integer)
23:50:16,016 DEBUG updateUser:159 - <==    Updates: 1
23:53:18,316 DEBUG JdbcTransaction:70 - Committing JDBC Connection [[email protected]]
23:53:22,306 DEBUG findById:159 - ==>  Preparing: select * from user where id=? 
23:53:22,306 DEBUG findById:159 - ==> Parameters: 1(Integer)
23:53:22,307 DEBUG findById:159 - <==      Total: 1
======User{id=1, username=' Lee thought ', roleList=null, orderList=null}

We see , And finally printed 3 strip sql, The second query after modification is also printed .

Indicates that the corresponding... Cannot be found in the cache during the second query key 了 . In the modification operation , Refresh cache

We can also pass sqlSession.clearCache(); Refresh the first level cache manually

summary :

  • First level data structure HashMap
  • Different SqlSession The first level of cache does not affect each other
  • First level cache key It's made up of multiple values ,value It's the query result
  • Adding, deleting and modifying will refresh the first level cache
  • adopt sqlSession.clearCache() Refresh the first level cache manually

First level cache source code analysis :

Before we analyze the L1 cache Read the code with some questions

  1. What is the first level cache ? It's really what the above says HashMap Do you ?

  2. When the L1 cache is created ?

  3. What is the workflow of L1 cache ?

1. What is the first level cache ?

I said before Different SqlSession The first level of cache does not affect each other , So I went from SqlSession Start with this class
image-20201111005238379

You can see ,org.apache.ibatis.session.SqlSession There is a cache related method in ——clearCache() How to refresh the cache , Click in , Find its implementation class DefaultSqlSession

  @Override
  public void clearCache() {
    executor.clearLocalCache();
  }

Click in again executor.clearLocalCache(), Click in again and find the implementation class BaseExecutor,

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

Get into localCache.clear() Method . Into org.apache.ibatis.cache.impl.PerpetualCache Class

package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {
  private final String id;

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

  public PerpetualCache(String id) {
    this.id = id;
  }

  // Omit the part ...
  @Override
  public void clear() {
    cache.clear();
  }
  // Omit the part ...
}

We see that PerpetualCache There is an attribute in the class private Map<Object, Object> cache = new HashMap<Object, Object>(), Obviously it's a HashMap, What we call .clear() Method , It's actually called Map Of clear Method
image-20201111010052591

Come to the conclusion :

The data structure of L1 cache is really HashMap
image-20201111011712449

2. When the L1 cache is created ?

We enter org.apache.ibatis.executor.Executor in
See a way CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) , To see the name and think means to create CacheKey Methods
Find its implementation class and method org.apache.ibatis.executor.BaseExecuto.createCacheKey

image-20201111012213242

Let's analyze the creation of CacheKey This code of :

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // initialization CacheKey
    CacheKey cacheKey = new CacheKey();
    // Deposit in statementId
    cacheKey.update(ms.getId());
    // Save the required pages separately Offset and Limit
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    // Take from BoundSql The encapsulation of sql Take out and deposit in cacheKey In the object 
    cacheKey.update(boundSql.getSql());
    // Here's the package parameter 
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    // from configuration In the object ( That is, the object stored after loading the configuration file ) hold EnvironmentId Deposit in 
        /**
     *     <environments default="development">
     *         <environment id="development"> // This is this. id
     *             <!-- The present business is left to JDBC Conduct management -->
     *             <transactionManager type="JDBC"></transactionManager>
     *             <!-- The current use of mybatis Provided connection pool -->
     *             <dataSource type="POOLED">
     *                 <property name="driver" value="${jdbc.driver}"/>
     *                 <property name="url" value="${jdbc.url}"/>
     *                 <property name="username" value="${jdbc.username}"/>
     *                 <property name="password" value="${jdbc.password}"/>
     *             </dataSource>
     *         </environment>
     *     </environments>
     */
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    // return 
    return cacheKey;
  }

Let's go in cacheKey.update() Take a look at

/**
 * @author Clinton Begin
 */
public class CacheKey implements Cloneable, Serializable {
  private static final long serialVersionUID = 1146682552656046210L;
  public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private final int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  // Where the value is deposited 
  private transient List<Object> updateList;
  // Omission method ......
  // Omission method ......
  public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;
    // A value is passed in to see list in 
    updateList.add(object);
  }
 
  // Omission method ......
}

We know that the data is in CacheKey How to store in the object . Let's go back to createCacheKey() Method .
image-20201111013322287

Ctrl+ Left mouse button Click on the method name , Query where this method is called

We enter BaseExecutor, You can see a query() Method :
image-20201111013443089

Here we can see clearly , In execution query() Before the method ,CacheKey Methods are created

3. The execution flow of L1 cache

We can see , establish CacheKey Then called. query() Method , Let's click in again :

image-20201111014034187

In execution SQL How to find in the first level cache Key, Then it will be executed sql, Let's take a look at execution sql What will be done before and after , Get into list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
image-20201111014639468

Look at the :

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //1.  hold key Deposited in the cache ,value Put a place holder 
	localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //2.  Interacting with the database 
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //3.  If the first 2 What's wrong with it , The first 1 Step deposited key Delete 
      localCache.removeObject(key);
    }
      //4.  Cache the results 
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

thus , Our thinking is very clear .

Conclusion :

In execution sql front , According to CacheKey Is there any query in the cache , If there is , Just deal with the parameters in the cache , without , Is executed sql, perform sql Then put the results in the cache .

First level cache source code analysis conclusion :

  1. The data structure of L1 cache is a HashMap<Object,Object>, its value It's the query result , its key yes CacheKey,CacheKey There is one of them. list attribute ,statementId,params,rowbounds,sql All the parameters are stored in this list in
  2. The first level cache is called query() Method is created before . And passed in to query() In the method
  3. According to CacheKey Is there any query in the cache , If there is , Just deal with the parameters in the cache , without , Is executed sql, perform sql Then put the results in the cache .

版权声明
本文为[IsDxh]所创,转载请带上原文链接,感谢

Scroll to Top