Mybatis cache source code and distributed cache application

Mybatis

mybatis loading process

  1. The Config.xml global configuration file is parsed and placed in the configuration object. The annotation configuration and SQL configuration files in Java are encapsulated in the statement object and stored in memory, which is also maintained by the configuration object

  2. Mapping file loading: the mappedStatement object parses the sql, and each sql object corresponds to a statementID, and stores the parsed sql in the map, the key is the statementId, and the value is the parsed sql, which is stored in the sqlsource

  3. sqlSource stores sql information and also provides sql parsing functions

    dynamicsqlSource:${} parse this kind of dynamic sql

    RawsqlSource:#{}Analyze this kind of dynamic sql

    StaticsqlSource: After parsing the above two kinds of sql, store it in sqlSource, through this sqlSource can get BoundSql
    combined with staticSqlSource source code, it will be easier to understand

public class StaticSqlSource implements SqlSource {
    
    

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    
    
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    
    
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}
  1. BoundSql:

    String sql; parsed sql

    List ParameterMappings; corresponding to the incoming parameters

    sqlNode: tree structure

    sqlSource stores sql information, which is a collection of sqlNode

Mybatis first level cache

Mybatis has a first-level cache and a second-level cache

Level 1 cache

Introduction

The first level cache is based on sqlSession

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);
// 第二次查询
Demo demo2 = demoMapper.findById(1);

The above code uses the same demoMapper for two consecutive queries. The first query will indeed connect and query the database, and the second time, it will go to the first level cache of mybatis to fetch it. That is, after the first query, the query result will be stored in the first-level cache, and then the query will be searched in the first-level cache again after the query, and it will be returned if it matches, and it will be queried in the database if it does not match.

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);

//更新
Demo demo = new User();
demo.setId(1);
demo.setName("test");
demoMapper.updateDemo(demo);
// 第二次查询
Demo demo2 = demoMapper.findById(1);

In the above code, after the first query, the data is updated again, and then the second query is performed.

Process :

  1. The first query connects to the database and stores the query results in the first-level cache.
  2. When the data is updated, the update operation will clear the first-level cache, so the corresponding cache in the first-level cache is emptied at this time.
  3. In the second query, it will not be obtained in the first-level cache, so go to the database to query.

Source code analysis

We find the SqlSesion interface. The one that seems to have an effect on the first level cache is a clearCache() method

Insert picture description here

1. Enter the implementation of the () method of clearCache DefaultSqlSession

Insert picture description here

The source code is as follows: For the convenience of viewing, I will not take a screenshot and paste the source code directly

  public void clearCache() {
    
    
    this.executor.clearLocalCache();
  }
2. Enter the Executor interface

Insert picture description here

3. Enter the implementation of the clearLocalCache() method in Executor

The source code implemented in the BaseExecutor class is as follows

 public void clearLocalCache() {
    
    
    if (!this.closed) {
    
    
      this.localCache.clear();
      this.localOutputParameterCache.clear();
    }
  }
4. Enter the localCache.clear() method:

The source code of the PerpetualCache class is as follows

public void clear() {
    
    
    this.cache.clear();
 }
5.cache.clear(), what is the cache object?

Looking at the source code below, the original object that executes clear is just a simple map object. From here we can know that the first level cache of mybatis is stored using Map.
Insert picture description here

-------------------------------------------------- --------------------Dividing line---------------------------- ------------------------------------------------

The source code introduced above is the clearing of the first-level cache. What about creation?

Let's review the process of the above source code

SqlSession–DefaultSqlSession–Executor–BaseExecutor–PerpetualCache

What is Executor? Executor, we know that the executor is used to execute the SQL request. Since the emptying of the cache is executed in the exctor, the operation of storing the result and storing the result together with the cache after executing the SQL request must also be executed here.

1. The method in Executor, the method for creating a cache is the createCacheKey() method

Insert picture description here

2. Go to the implementation class BaseExecutor to see the specific implementation

The source code is as follows:

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

Note that this line of code CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

The source code of createCacheKey() method here: The content is directly written to the comment of the following code

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    
    
    if (closed) {
    
    
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
    cacheKey.update(ms.getId());
    // 就是SQL中使用的offset,控制查询范围的那种
    cacheKey.update(rowBounds.getOffset());
    // SQL中有没有limit限制查询数量
    cacheKey.update(rowBounds.getLimit());
    // 这里就是具体的SQL语句了
    cacheKey.update(boundSql.getSql());
    
    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);
        }
        
        // 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
    
    
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

To sum up: the first level cache is created here. What is stored in it has been remarked in the comment above, and I will list it below

CacheKey cacheKey = new CacheKey();
// boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
cacheKey.update(ms.getId());
// 就是SQL中使用的offset,控制查询范围的那种
cacheKey.update(rowBounds.getOffset());
// SQL中有没有limit限制查询数量
cacheKey.update(rowBounds.getLimit());
// 这里就是具体的SQL语句了
cacheKey.update(boundSql.getSql());
// 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
cacheKey.update(value);

Secondary cache

Introduction

The secondary cache also stores query results and the corresponding SQL. After the query is completed, it is placed in the cache, and the query can be retrieved from it again.

So what is the difference between it and the first level cache

The first level cache has been introduced above and is based on SQLSession, that is, the same sqlSession.

The second-level cache is based on the nameSpace of the mapper, which is the second-level cache of a mapper (or multiple mappers with the same nameSpace). There can be multiple sqlSessions.

And the second level cache needs to be manually turned on

use

Add the following code in the global configuration file
Insert picture description here

Then add the following code to the specific mapper you need to use

Insert picture description here

The implementation class used by mybatis by default is the PerpetualCache cache class traced from the above source code, which implements the cache interface. So here we can also define the implementation class of the second-level cache and implement the cache interface.

Distributed secondary cache

How does mybatis implement distributed caching? If you operate according to the above cache, it is only a single cache.

Distributed cache: multiple servers can access the operation cache

Here we can use Redis combined with mybatis to implement distributed caching

The method of use is as follows:

1. Pom file increase dependency

<dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
</dependency>

2. Specify the cache implementation class to be used in the mapper used

<cache type="org.mybatis.caches.redis.RedisCache"></cache>

3. Configure redis information, etc.
Insert picture description here

Source code analysis

Let's take a look at the secondary cache implemented by redisCache

1. Construction method

From the code here, we can see that the secondary cache implemented by Redis uses jedis to operate.

 public RedisCache(final String id) {
    
    
   if (id == null) {
    
    
     throw new IllegalArgumentException("Cache instances require an ID");
   }
   this.id = id;
   RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
  redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
  redisConfig.getDatabase(), redisConfig.getClientName());
 }
2. Put get method. From here, what is the storage structure of the secondary cache?

From the hset command, it is easy to know that the hash structure is used to store cached data

@Override
public void putObject(final Object key, final Object value) {
    
    
  execute(new RedisCallback() {
    
    
    @Override
    public Object doWithRedis(Jedis jedis) {
    
    
      jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
      return null;
    }
  });
}

@Override
public Object getObject(final Object key) {
    
    
  return execute(new RedisCallback() {
    
    
    @Override
    public Object doWithRedis(Jedis jedis) {
    
    
      return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
    }
  });
}

Guess you like

Origin blog.csdn.net/weixin_44969687/article/details/115331591