MyBatis cache description
MyBatis provides two levels of cache, namely first-level cache and second-level cache. The first-level cache is a SqlSession-level cache. It only stores cached data inside the SqlSession object. If the SqlSession objects are different, the cache cannot be hit. The second-level cache is a mapper-level cache. As long as the Mapper class used is the same, the cache can be shared.
When querying data, Mybatis will first query the second-level cache. If the second-level cache is not available, it will query the first-level cache. If it is not available, the database query will be performed.
Mybatis’s first-level cache is enabled by default, while the second-level cache needs to be enabled manually in the mapper.xml configuration file or through the @CacheNamespace annotation.
It should be noted that when Spring is integrated, the transaction first-level cache must be enabled to take effect, because if the cache is not enabled, a SqlSession object will be re-created for each query, so the cache cannot be shared.
Enable the second-level cache of a Mapper through @CacheNamespace.
@Mapper @CacheNamespace public interface EmployeeMapper extends BaseMapper<Employee> {<!-- --> }
Enable all second-level caches:
mybatis-plus: mapper-locations: classpath:mybatis/mapper/*.xml configuration: cache-enabled: true
MybatisPlus integrates Redis to implement distributed second-level cache
The built-in secondary cache of Mybatis has distribution problems in a distributed environment and cannot be used. However, we can integrate Redis to implement distributed secondary cache.
1.Introduce dependencies
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.24.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency>
2. Configure RedisTemplate
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration @EnableCaching public class RedisConfiguration {<!-- --> private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer(); private static final GenericJackson2JsonRedisSerializer JACKSON__SERIALIZER = new GenericJackson2JsonRedisSerializer(); @Bean @Primary public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {<!-- --> //Set cache expiration time RedisCacheConfiguration redisCacheCfg = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(JACKSON__SERIALIZER)); return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)) .cacheDefaults(redisCacheCfg) .build(); } @Bean @Primary @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {<!-- --> //Configure redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); // key serialization redisTemplate.setKeySerializer(STRING_SERIALIZER); // value serialization redisTemplate.setValueSerializer(JACKSON__SERIALIZER); // Hash key serialization redisTemplate.setHashKeySerializer(STRING_SERIALIZER); // Hash value serialization redisTemplate.setHashValueSerializer(JACKSON__SERIALIZER); // Set up support transactions redisTemplate.setEnableTransactionSupport(true); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public RedisSerializer<Object> redisSerializer() {<!-- --> //Create JSON serializer ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //Must be set, otherwise JSON cannot be converted into an object and will be converted into a Map type objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); return new GenericJackson2JsonRedisSerializer(objectMapper); } }
3. Custom cache class
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.cache.Cache; import org.redisson.api.RReadWriteLock; import org.redisson.api.RedissonClient; import org.springframework.data.redis.connection.RedisServerCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; @Slf4j public class MybatisRedisCache implements Cache {<!-- --> // redisson read-write lock private final RReadWriteLock redissonReadWriteLock; // redisTemplate private final RedisTemplate redisTemplate; // cache ID private final String id; //Expiration time 10 minutes private final long expirationTime = 1000*60*10; public MybatisRedisCache(String id) {<!-- --> this.id = id; //Get redisTemplate this.redisTemplate = SpringUtil.getBean(RedisTemplate.class); //Create a read-write lock this.redissonReadWriteLock = SpringUtil.getBean(RedissonClient.class).getReadWriteLock("mybatis-cache-lock:" + this.id); } @Override public void putObject(Object key, Object value) {<!-- --> //Use the Hash type of redis for storage redisTemplate.opsForValue().set(getCacheKey(key),value,expirationTime, TimeUnit.MILLISECONDS); } @Override public Object getObject(Object key) {<!-- --> try {<!-- --> //Get data from redis based on key Object cacheData = redisTemplate.opsForValue().get(getCacheKey(key)); log.debug("[Mybatis second-level cache] Query cache,cacheKey={},data={}",getCacheKey(key), JSONUtil.toJsonStr(cacheData)); return cacheData; } catch (Exception e) {<!-- --> log.error("Caching error",e); } return null; } @Override public Object removeObject(Object key) {<!-- --> if (key != null) {<!-- --> log.debug("[Mybatis second-level cache] delete cache,cacheKey={}",getCacheKey(key)); redisTemplate.delete(key.toString()); } return null; } @Override public void clear() {<!-- --> log.debug("[Mybatis second-level cache] Clear cache, id={}",getCachePrefix()); Set keys = redisTemplate.keys(getCachePrefix() + ":*"); redisTemplate.delete(keys); } @Override public int getSize() {<!-- --> Long size = (Long) redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize); return size.intValue(); } @Override public ReadWriteLock getReadWriteLock() {<!-- --> return this.redissonReadWriteLock; } @Override public String getId() {<!-- --> return this.id; } public String getCachePrefix(){<!-- --> return "mybatis-cache:%s".formatted(this.id); } private String getCacheKey(Object key){<!-- --> return getCachePrefix() + ":" + key; } }
4. Enable secondary cache on the Mapper interface
//Enable the second level cache and specify the cache class @CacheNamespace(implementation = MybatisRedisCache.class,eviction = MybatisRedisCache.class) @Mapper public interface EmployeeMapper extends BaseMapper<Employee> {<!-- --> }