What is distributed caching?
In the original application, the cache is usually stored in the JVM of the local application. However, with the trend of project microservicing, after the project is reconstructed in the way of microservice, the cache should be managed and stored by a separate server and deployed in different physical areas to achieve high availability.
Mybatis original cache storage method
When the distributed cache is not used, mybatis has its own cache. Mybatis stores the results of each query in the HashMap attribute of a class, The cache processing interface of mybatis is org.apache.ibatis.cache. The implementation class of cache operation is org.apache.ibatis.cache.impl PerpetualCache Mybatis caches the queried data into the cache attribute of the secondary implementation class.
The following is part of the source code
public class PerpetualCache implements Cache { //Cache id private final String id; //Final storage cache private final Map<Object, Object> cache = new HashMap(); public PerpetualCache(String id) { this.id = id; } //Query cache id public String getId() { return this.id; } //Get cache size public int getSize() { return this.cache.size(); } //Add data to cache public void putObject(Object key, Object value) { this.cache.put(key, value); } //Get cache public Object getObject(Object key) { return this.cache.get(key); } //This method mybatis does not implement. The caller is a reserved method public Object removeObject(Object key) { return this.cache.remove(key); } //Clear cache public void clear() { this.cache.clear(); } //... the source code is not fully displayed
Steps:
When the request arrives, access the dao layer in the application. mybatis resolves whether the < cache / > cache tag is added in the mapping file, indicating that the cache is enabled. In the default cache implementation class PerpetualCache, use the getObject(Object key) method to try to get the cache. If yes, the contents in the cache will be returned. Otherwise, the database will be queried, and the obtained results will be stored in the cache and returned to the user.
Implementation principle of distributed cache
The environment of this example is SpringBoot + Mybatis + Redis as the basic environment of distributed cache. Mybatis is used as the operator for storing, obtaining and deleting the cache, and Redis is used as the storer of the cache.
Steps:
When the request arrives, access the dao layer in the application. mybatis resolves whether the < cache / > cache tag is added in the mapping file, indicating that the cache is enabled.
If the class cache is enabled, the implementation class specified by us will be used to obtain the cache in redis. If yes, the cache will be returned. If not, the data will be queried from the database, and then stored in redis and returned to the user.
Our purpose is to replace the default cache processing class in mybatis and let it use the implementation class provided by us to operate the cache in Redis.
Implementation example
Dependencies:
Omit Springboot dependency
Database three piece set
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency>
Use this dependency operation to Redis database
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
step
Note that the entity class is implemented as a serialization interface
- Create a normal SSM project
- Configure the application.
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 907282050 url: jdbc:mysql://127.0.0.1:3306/db_test?serverTimezone=Asia/Shanghai&useSSL=false type: com.alibaba.druid.pool.DruidDataSource # Configure redis connection information redis: host: localhost port: 6379 database: 0 mybatis: mapper-locations: classpath:mappers/*.xml type-handlers-package: com.ccnn.test_01.com.ccnn.test_01.entity logging: level: com.ccnn.test_01.dao: DEBUG
- Get the RedisTemplate object injected into the spring boot container to operate redis
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public static Object getRedisTemplate(String name){ return applicationContext.getBean(name); } }
- The custom class implements the org.apache.ibatis.cache.Cache interface and its methods
import com.ccnn.test_01.jedis.ApplicationContextUtil; import org.apache.ibatis.cache.Cache; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; public class RedisCache implements Cache { /* id The value of is the fully qualified class name of this class (com.ccnn.test_01.dao.UserMapper) and the namespace in the mapper.xml mapping file The values in the label are the same*/ private final String id; /* RedisTemplate is used as a property declaration for convenience*/ private final RedisTemplate<Object,Object> redisTemplate; public RedisCache(String id) { this.id = id; /* Get RedisTemplate object and set serialization rules */ redisTemplate = (RedisTemplate<Object,Object> ) ApplicationContextUtil.getRedisTemplate("redisTemplate"); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setKeySerializer(new StringRedisSerializer()); } @Override public String getId() { return this.id; } /* Get data from cache If no data is obtained in the cache, it will be queried from the database, Use putObject() of this class for the queried data; Add to cache Storage (default cache implementation class used in mybatis: org.apache. ORG) ibatis.cache.impl.PerpetualCache A HashMap object is used in (for storage) Therefore, here we take the data from Redis and return it as cache. */ @Override public Object getObject(Object o) { /*Here, the parameter O and O in putObject(Object o, Object o1) are the same parameter*/ System.out.println("Get data——————————"+o) return redisTemplate.opsForHash().get(id,o.toString()); } /* Store data to cache If no result is found in the cache, it will be queried from the database Stored in the cache, so the data is stored in Redis here. */ @Override public void putObject(Object o, Object o1) { //The following is the output of the parameter /*2057322197:1276118677:com.ccnn.test_01.dao.UserMapper. selectUser:0:2147483647:select * from user where name = ?:Xiao Li: sqlsessionfactorybean - [user {name = 'Xiao Li', age=15}]*/ System.out.println(o.toString()+"---"+o1.toString()); redisTemplate.opsForHash().put(id,o.toString(),o1); } /*mybatis Calls that do not implement this send have no callers*/ @Override public Object removeObject(Object o) { return null; } //Remove data from cache @Override public void clear() { redisTemplate.delete(id); } @Override public int getSize() { return redisTemplate.opsForHash().size(id).intValue(); } //Encryption method private static String getKeyToM5(String k){ return DigestUtils.md5DigestAsHex(k.getBytes()); } }
When updating, deleting and adding, the value in the redis database will be deleted according to the value of the id attribute. In the next query, the value will be added from mysql to redis.
- Add < cache type = "custom implementation class" / > to Mapper.xml mapping file
<?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.ccnn.test_01.dao.UserMapper"> <!--Specifies the cache processing class--> <cache type="com.ccnn.test_01.cache.RedisCache"/> <select id="selectUser" resultType="com.ccnn.test_01.entity.User" parameterType="String"> select * from user where name = #{name} </select> <update id="updateUser" parameterType="String"> update user set name = #{name} where age = 15 </update> </mapper>
- It is suitable for multi table joint query.
During multi table associated query, please modify the cache tag of the main party shared by one of the two associated mapper.xml files to:
<?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.ccnn.test_01.dao.EmpMapper"> <cache-ref namespace="Shared mapper.xml The value of the namespace of the file"/> <select > ....... </select> </mapper>
Enable both parties to operate the cache in a redis. When either party modifies the data, the cache will be cleared.
optimization
Optimize the cached data stored in redis.
When we see the key of the printed cached data, we find that the key is too long, so we use the MD5 encryption tool class of spring connotation (the value obtained after encrypting two files with the same content must be the same) to encrypt, generate a 32-bit hexadecimal string, and store it in Redis as a key. Decrypt when getting the cache.
Added above.