Mybatis_04_mybatis L2 cache

Introduction:

1. Why cache

Pull up program performance

2. What kind of data needs to be cached

Data that is rarely modified or not modified at all

Business scenarios, such as time-consuming statistical analysis sql, telephone bill query sql, etc

3. What is ehcache

Ehcache is the most popular pure Java open source caching framework, with simple configuration, clear structure and powerful functions

Note 1: this chapter introduces 2 Version x, 3 X version and 2 The API versions of X are quite different

4. Features of ehcache

4.1 fast enough

Ehcache has been released for a long time. After years of efforts and countless performance tests, ehcache was finally designed in large, high concurrency systems

4.2 simple enough

The interface provided by developers is very simple and clear. It only takes you a few precious minutes from ehcache construction to application and operation. In fact, many developers do not know they are using ehcache. Ehcache is widely used in other open source projects

4.3 pocket size

For this feature, the official gave a lovely name small foot print. Generally, the release version of Ehcache will not reach 2M, V 2.2 3 is only 668KB.

4.4 light enough

The core program only depends on slf4j this package, not one!

4.5 good expansion

Ehcache provides memory and hard disk storage for big data. The latest version allows multiple instances, high flexibility of saving objects, LRU, LFU and FIFO elimination algorithms, and basic attributes support hot configuration and multiple plug-ins

4.6 listener

It's very useful for the cache manager listener and cache evenlistener to broadcast statistics or data consistency

4.7 distributed cache

Starting from Ehcache 1.2, it supports high-performance distributed cache with flexibility and scalability

3. Use of ehcache

3.1 import related dependencies

3.2 core interface

CacheManager: cache manager

Cache: cache object. Several caches can be placed in the cache manager to store the essence of data. All caches implement the Ehcache interface

Element: the constituent unit of a single piece of cached data

I. mybatis uses Ehcache as the L2 cache

Import POM XML dependency:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>
 
<!--mybatis And ehcache integration-->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>
 
<!--ehcache rely on-->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>

Modify log configuration:
The log uses slf4j and is implemented with log4j. Slf4j is very different from other log class libraries.

slf4j(Simple logging Facade for Java) is not a real logging implementation, but an abstraction layer,

It allows you to use any log class library in the background.

slf4j is an abstract logging framework. Its advantage is that it has no specific implementation. Who will implement it depends on the framework itself

Replace with log4j2 log configuration related dependencies

 <!-- log4j2 Log configuration related dependencies -->
<log4j2.version>2.9.1</log4j2.version>
<log4j2.disruptor.version>3.2.0</log4j2.disruptor.version>
<slf4j.version>1.7.13</slf4j.version>


Replace with log4j2 log related dependencies

<!-- log4j2 Log dependent dependencies -->
    <!-- log to configure: Log4j2 + Slf4j -->
    <!-- slf4j Core package-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>${slf4j.version}</version>
      <scope>runtime</scope>
    </dependency>
 
    <!--core log4j2jar package-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>${log4j2.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>${log4j2.version}</version>
    </dependency>
    <!--Used with slf4j Keep bridging-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>${log4j2.version}</version>
    </dependency>
    <!--web The project needs to include log4j-web,wrong web The project does not require-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-web</artifactId>
      <version>${log4j2.version}</version>
      <scope>runtime</scope>
    </dependency>
 
    <!--Need to use log4j2 of AsyncLogger Need to include disruptor-->
    <dependency>
      <groupId>com.lmax</groupId>
      <artifactId>disruptor</artifactId>
      <version>${log4j2.disruptor.version}</version>
    </dependency>

Add ehcache xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--Disk storage:Temporarily unused objects in the cache,Transfer to hard disk,be similar to Windows Virtual memory of the system-->
    <!--path:Specifies the path to store objects on the hard disk-->
    <!--java.io.tmpdir Is the default temporary file path. You can print out the specific file path in the following ways System.out.println(System.getProperty("java.io.tmpdir"));-->
    <diskStore path="java.io.tmpdir"/>
 
 
    <!--defaultCache: Default management policy-->
    <!--eternal: Set cached elements Whether to never expire. If yes true,The cached data is always valid if false Then according to timeToIdleSeconds,timeToLiveSeconds judge-->
    <!--maxElementsInMemory: Cached in memory element Maximum number of-->
    <!--overflowToDisk: If the data in memory exceeds the memory limit, do you want to cache it to disk-->
    <!--diskPersistent: Whether to persist on disk. Means restart jvm Whether the data is valid after. Default to false-->
    <!--timeToIdleSeconds: Object idle time(Unit: Second),It refers to how long an object will become invalid if it is not accessed. Only right eternal by false The is valid. The default value is 0, which means it can always be accessed-->
    <!--timeToLiveSeconds: Object lifetime(Unit: Second),It refers to the time required for an object from creation to expiration. Only right eternal by false The is valid. The default value is 0, which means it can always be accessed-->
    <!--memoryStoreEvictionPolicy: Three cache emptying strategies-->
    <!--FIFO: first in first out (fifo)-->
    <!--LFU: Less Frequently Used (Minimum use).It means the least used all the time. Cached element has a hit Properties, hit The smallest value will be flushed out of the cache-->
    <!--LRU: Least Recently Used(Least recently used). (ehcache Default value).The cached element has a timestamp. When the cache capacity is full and it needs to make room for caching new elements, the element with the farthest timestamp from the current time in the existing cache element will be cleared out of the cache-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
 
    <!--name:  Cache The name of the must be unique(ehcache I'll take this cache put to HashMap in)-->
    <!--<cache name="stuCache" eternal="false" maxElementsInMemory="100"-->
           <!--overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"-->
           <!--timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/>-->
</ehcache>

In ApplicationContext Adding chache configuration to XML

Copy ApplicationContext mybatis Create applicationContext.xml XML file

Put ApplicationContext XML as the total number of spring files

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <import resource="classpath:applicationContext-mybatis.xml"></import>
    <import resource="classpath:applicationContext-ehcache.xml"></import>
</beans>

Create ApplicationContext ehcache xml

It stores ehcache XML cache configuration, so that ehcache can be managed by sparing

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <!-- use ehcache cache -->
    <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
        <property name="shared" value="true"></property>
    </bean>
    <!-- The default is cacheManager -->
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="cacheManagerFactory"/>
    </bean>
</beans>

The second level cache of mybaits is the mapper range level, except in sqlmapconfig XML to set the master switch of L2 cache, and also in the specific mapper Enable L2 cache in XML

Enable the L2 cache of mybatis:

applicationContext-mybatis. Add to XML

<!--set up mybaits Cache support-->
<property name="configurationProperties">
    <props>
        <!-- Global mapper enable caching *Mainly set this property-->
        <prop key="cacheEnabled">true</prop>
        <!-- When querying, turn off immediate loading of associated objects to improve performance -->
        <prop key="lazyLoadingEnabled">false</prop>
        <!-- Set the loading mode of associated objects. Here is the on-demand loading field(Loading fields by SQL appoint),All fields of the associated table are not loaded to improve performance -->
        <prop key="aggressiveLazyLoading">true</prop>
    </props>
</property>

BookMapper. Add L2 cache core class to XML

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

Execution results:

The L2 cache is used successfully, and the query statement appears only once:

The mybatis framework caches multiple pieces of data by default:

The L2 cache can be turned on or off through the useCache attribute of the select tag

        <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long" useCache="false"></select>

be careful:

The second level cache framework used by mybatis by default is ehcache (org.mybatis.caches.ehcache.EhcacheCache), which combines seamlessly
Once the Mybatis cache switch is turned on, you can cache a single record or multiple records. hibernate cannot cache multiple records.
All methods on the Mapper interface additionally provide the property to close the cache

Second direct access to cache:

All queries will enter the cache the second time

Querying multiple pieces of data into L2 cache is not allowed because it will reduce performance

useCache="false"

2, mybatis uses redis as the L2 cache

1. Common redis classes

  1.1 Jedis

jedis integrates some command operations of redis and encapsulates the java client of redis

  1.2 JedisPoolConfig

Redis connection pool

  1.3 ShardedJedis

Distributed Redis cluster client based on consistent hash algorithm

Generally speaking, there are two ways to implement the secondary cache of mybatis:

(1) adopt the built-in cache mechanism of mybatis.

(2) adopt the third-party cache framework, such as ehcache, oscache, etc

First, import the dependency of Redis:

 <!-- redis And spring Integration dependency -->
    <redis.version>2.9.0</redis.version>
    <redis.spring.version>1.7.1.RELEASE</redis.spring.version>
 
 
Note: don't put it in the wrong position!!!
 
<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>${redis.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>${redis.spring.version}</version>
    </dependency>

Open rdm software and connect to Redis:

Import jackson related configuration (for serialization in Redis):

 <!-- jackson -->
        <jackson.version>2.9.3</jackson.version>
 
Note: not all copy,Pay attention to import dependencies separately!!!
 
 
 
<!-- jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>

spring + redis integration enables caching (not related to mybatis)

First, establish Redis Properties configuration of non relational database (be sure to modify the host name of your Redis connection)

redis.hostName=(own ip (address)
redis.port=6379
redis.password=(Own password)
redis.timeout=10000
redis.maxIdle=300
redis.maxTotal=1000
redis.maxWaitMillis=1000
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true

applicationContext-redis.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!-- 1. introduce properties configuration file -->
    <!--<context:property-placeholder location="classpath:redis.properties" />-->
 
    <!-- 2. redis Connection pool configuration-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--Maximum idle-->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!--Maximum number of database connections to the connection pool  -->
        <property name="maxTotal" value="${redis.maxTotal}"/>
        <!--Maximum connection setup wait time-->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <!--The minimum idle time for evicting connections is 1800000 milliseconds by default(30 minute)-->
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
        <!--The maximum number of evictions per eviction check is negative : 1/abs(n), Default 3-->
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
        <!--Time interval of eviction scan(millisecond) If negative,The eviction thread is not run, default-1-->
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
        <!--Are connections verified before they are removed from the pool,If the inspection fails,Then remove the connection from the pool and try to fetch another one-->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
        <!--Check validity when idle, default false  -->
        <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
    </bean>
 
    <!-- 3. redis Connection factory -->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          destroy-method="destroy">
        <property name="poolConfig" ref="poolConfig"/>
        <!--IP address -->
        <property name="hostName" value="${redis.hostName}"/>
        <!--Port number  -->
        <property name="port" value="${redis.port}"/>
        <!--If Redis Set password  -->
        <property name="password" value="${redis.password}"/>
        <!--The client timeout is in milliseconds  -->
        <property name="timeout" value="${redis.timeout}"/>
    </bean>
 
    <!-- 4. redis Operation template,Use this object to manipulate redis  -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--If not configured Serializer,It is used by default when storing String,If used User Type storage, an error will be prompted User can't cast to String!!  -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <!--Open transaction  -->
        <property name="enableTransactionSupport" value="true"/>
    </bean>
 
    <!-- 5.Using intermediate classes to solve RedisCache.RedisTemplate Static injection, so that MyBatis Implement third-party caching -->
    <bean id="redisCacheTransfer" class="com.lsy.util.RedisCacheTransfer">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>
</beans>

applicationContext.xml: firstly, annotate the original scattered data sources, and configure the multi data source import configuration in the general file entry

<context:property-placeholder location="classpath:jdbc.properties,classpath:redis.properties"/>

Create mybatis's custom cache class "RedisCache.java", which must implement org apache. ibatis. cache. Cache interface
RedisCache.java:

package com.dzl.util;
 
 
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
 
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 
public class RedisCache implements Cache //Implementation class
{
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
 
    private static RedisTemplate<String,Object> redisTemplate;
 
    private final String id;
 
    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 
    @Override
    public ReadWriteLock getReadWriteLock()
    {
        return this.readWriteLock;
    }
 
    public static void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisCache.redisTemplate = redisTemplate;
    }
 
    public RedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug("MybatisRedisCache:id=" + id);
        this.id = id;
    }
 
    @Override
    public String getId() {
        return this.id;
    }
 
    @Override
    public void putObject(Object key, Object value) {
        try{
            logger.info("----------------------putObject: key="+key+",value="+value);
            if(null!=value){
                redisTemplate.opsForValue().set(key.toString(),value,60, TimeUnit.SECONDS);
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error("redis Save data exception!");
        }
    }
 
    @Override
    public Object getObject(Object key) {
        try{
            logger.info("----------------------getObject: key="+key);
            if(null!=key){
                return redisTemplate.opsForValue().get(key.toString());
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error("redis Get data exception!");
        }
        return null;
    }
 
    @Override
    public Object removeObject(Object key) {
        try{
            if(null!=key) {
                return redisTemplate.expire(key.toString(), 1, TimeUnit.DAYS);
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error("redis Get data exception!");
        }
        return null;
    }
 
    @Override
    public void clear() {
        Long size=redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Long size = redisConnection.dbSize();
                //Connection clear data
                redisConnection.flushDb();
                redisConnection.flushAll();
                return size;
            }
        });
        logger.info("----------------------clear: wipe out" + size + "Objects");
    }
 
    @Override
    public int getSize() {
        Long size = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                return connection.dbSize();
            }
        });
        return size.intValue();
    }
}

Statically inject the intermediate class "RedisCacheTransfer.java" to solve the static injection of RedisTemplate in RedisCache, so that MyBatis can realize the third-party cache
RedisCacheTransfer.java:

package com.dzl.util;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
 
public class RedisCacheTransfer {
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
 
        RedisCache.setRedisTemplate(redisTemplate);
    }
}

Test:

@Test
public void testCacheOne() {
    System.out.println(this.bookService.selectByPrimaryKey(38));
    System.out.println(this.bookService.selectByPrimaryKey(38));
}

Here, the test passed and the data entered:

Keywords: Java Back-end

Added by davelr459 on Mon, 27 Dec 2021 11:39:49 +0200