编程知识 cdmana.com

Second level cache of mybatis, using redis as secondary cache

What is L2 cache ?

The principle of L2 cache and L1 cache is the same , First query , Will put the data in the cache , Then the second query will go directly to the cache . But the first level cache is based on sqlSession, The second level cache is based on mapper Of documents namespace Of , in other words Multiple sqlSession Sure share One mapper The secondary cache area in the , And how about two mapper Of namespace identical , Even two mapper, Then these two mapper In the implementation of sql The query data will also be stored in the same secondary cache area

image-20201111211828145

  • Pictured above sqlSession1 The query will be made from UserMapper Second level cache of To take , If not, perform the database query operation .
  • And then write it to the L2 cache
  • sqlSession2 Do the same thing UserMapper When inquiring , From UserMapper Second level cache of To take , At this point, there is already content in the secondary cache , So you can get it directly , No more interaction with the database .
  • sqlSession3 The transaction is performing an operation ( Insert 、 to update 、 Delete ) when , It will be emptied UserMapper Second level cache of

1. Enable L2 cache

How to use L2 cache :

mybatis in , The first level cache is enabled by default , But the secondary cache needs to be configured before it can be used

  1. In the global profile sqlMapConfig.xml Add the following code to :

    <!-- Enable L2 cache -->
    <settings>
    	<setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. Second, which namespace The configuration of secondary level is where it is opened , because mybatis There are notes and xml Two ways, so :

  • annotation
    image-20201111221707937
    Annotation extension :

    // By default, we use mybatis Built in L2 cache , It is implemented in PerpetualCache Class , So it can be written as 
    @CacheNamespace(implementation = PerpetualCache.class)
    // If using redis As a second level cache , The second part of this article will talk about 
    
  • xml
    image-20201111221501041
    So it turns on UserMapper Second level cache of

  1. Test one :

    We need to be based on the user id Query user information :

    image-20201111222635859

    Be careful : Will cache pojo Realization Serializable Interface , In order to extract the cache data, the deserialization operation is performed , Because the secondary cache has a variety of storage media , Not necessarily only in memory , It could be in the hard disk , If we're going to take out this cache again , You need to deserialize . therefore mybatis Of pojo To achieve Serializable Interface
    image-20201111222950505
    Finally, execute to see the print log :
    image-20201111223421674
    Why? System.out.println(user1==user2) by false ?

    L2 cache is different from L1 cache , The L2 cache does not cache objects , It's data , In the second query, the underlying layer recreates a User object , And the data in the secondary cache is repackaged into objects and returned . therefore user1 and user2 Not an object .

  2. Test two :
    Let's do a transaction operation in test two , See if you can empty the secondary cache :
    image-20201111224352601

    image-20201111224330557

​ Added a modification operation , It was found that two select, Indicates that the commit transaction will refresh the secondary cache

userCache and flushCache

It can also be configured userCache and flushCache

  • userCache : It is used to set whether to disable the secondary cache , stay statement Setting in can be Ban At present select sentence Second level cache of , That is to say, every query will send out sql. The default is true.

    image-20201111225137076
    image-20201111225544937

  • flushCache : stay mapper The same namespace in , If there are other add, delete and modify operations, you need to refresh the cache , If the cache is flushed, dirty reads will appear .
    Set up statement The configuration of the flushCache="true", That is to refresh the cache , If change to false It doesn't refresh , There may be dirty reading . So in general, there is no need to change
    image-20201111225901338

Mybatis The second level cache is used as well as the first level cache org.apache.ibatis.cache.impl.PerpetualCache

This class is mybatis The default cache class of , meanwhile , If you want to customize the cache, you have to implement cache Interface

image-20201112005725676

2. Use Redis Second level cache

Mybatis There are drawbacks to the built-in L2 cache , It's this cache that works on a single server , Can't implement distributed caching .
image-20201112010039059

So in order to solve this problem , We have to find a distributed cache to store cache data .
image-20201112010027075

How to use

mybatis Provides a target for cache Interface redis Implementation class , stay mybatis-redis In bag

  1. First let's introduce jar package

    <dependency> 
        <groupId>org.mybatis.caches</groupId> 
        <artifactId>mybatis-redis</artifactId> 
        <version>1.0.0-beta2</version> 
    </dependency>
    
  2. modify Mapper.xml file

    //**********XML The way ***********:
    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
    <mapper namespace="com.lagou.mapper.IUserMapper"> 
        // For the current namespace Enable L2 cache 
        <cache type="org.mybatis.caches.redis.RedisCache" /> 
        
        <select id="findAll" resultType="com.lagou.pojo.User" useCache="true"> 
        	select * from user 
        </select>
    
    //******* Annotation mode **********
    @CacheNamespace(implementation = RedisCache .class)
    public interface UserMapper {
        // according to id Query the user   Annotations use 
        @Select("select * from user where id=#{id}")
        public User findById(Integer id);
    
    

    This class also implements Cache Interface
    image-20201112011613642

  3. To configure redis Configuration file for

    redis.host=localhost
    redis.port=6379
    redis.connectionTimeout=5000
    redis.password=
    redis.database=0
    

    The test method is the same as the built-in L2 cache .

3. Redis Source code analysis of L2 cache

RedisCache and Mybatis The schemes for L2 caching are almost the same , Nothing but realization Cache Interface , And use jedis Operating the cache , But there are some differences in the design details .
We analyze the source code with problems :

  • stay RedisCache How does a class turn to redis To access the cache value ?
  • What kind of data structure is used ?
package org.mybatis.caches.redis;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

// First of all, it realizes Cache Interface , By mybatis During initialization CacheBuilder establish 
// The creation method is to call the following parameter construction 
public final class RedisCache implements Cache {
  private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
  private String id;
  private static JedisPool pool;
  // There are parametric structures   
  public RedisCache(final String id) {
    if (id == null) {
      throw new IllegalArgumentException("Cache instances require an ID");
    }
    this.id = id;
    //RedisConfigurationBuilder call parseConfiguration() Method creation RedisConfig object 
    RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
    // structure Jedis pool 
	pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
			redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
			redisConfig.getDatabase(), redisConfig.getClientName());
  }
  
  // Template method , Below putObject and getObject、removeObject It's used in this way 
  private Object execute(RedisCallback callback) {
    Jedis jedis = pool.getResource();
    try {
      return callback.doWithRedis(jedis);
    } finally {
      jedis.close();
    }
  }
    
  //........ Omitted code   
    
    
  @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()));
      }
    });
  }

  @Override
  public Object removeObject(final Object key) {
    return execute(new RedisCallback() {
      @Override
      public Object doWithRedis(Jedis jedis) {
        return jedis.hdel(id.toString(), key.toString());
      }
    });
  }


}

  1. RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
    image-20201112014625885
    RedisConfig The default Redis Configuration information

    image-20201112014920980
    This method reads our configuration in /resource/redis.properties This file
    RedisConfig After that, we built Jedis pool

  2. put Method

    private Object execute(RedisCallback callback) {
        Jedis jedis = pool.getResource();
        try {
          return callback.doWithRedis(jedis);
        } finally {
          jedis.close();
        }
      }
    
    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;
          }
        });
      }
    

    We can see ,put Method calls the template method to get One jedis link , And then call doWithRedis() Method

    jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
    

    You can see it clearly ,mybatis-redis When storing data , It is used. hash structure , hold cache Of id As this hash Of key (cache Of id stay mybatis The middle is mapper Of namespace); This mapper Query cache data in as hash Of field, What needs to be cached is used directly SerializeUtil Storage ,SerializeUtil Similar to other serialization classes , Responsible for the serialization and deserialization of objects ;

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

Scroll to Top