Mybatis二级缓存默认采用的org.apache.ibatis.cache.impl.PerpetualCache实现的(基于内存中Map
Mybatis支持Ehcache二级缓存配置,默认适用于单实例部署,亦可以支持分布式部署(配置较为复杂,维护困难,支持RMI(手动、自动 - 组播 )、JGroups、EhCache Server等部署方式);
关于Ehcache分布式部署可参见:
https://blog.csdn.net/tang06211015/article/details/52281551
http://blog.sina.com.cn/s/blog_6151984a0101816j.html
Ehcache & Redis对比:
(1)Ehcache(分布式缓存)直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
(2)Redis(集中式缓存)是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
因此考虑使用Redis支持分布式缓存,Mybatis官方提供mybatis-redis插件(http://www.mybatis.org/redis-cache/),官方插件需要单独配置/redis.properties并且维护一个JedisConfigPool,考虑到单独配置与项目已有Redis配置重复且无法复用本地配置,因此决定参照官方org.mybatis.caches.redis.RedisCache来重写自己的MybatisRedisCache 。
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.ibatis.cache.Cache; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.data.redis.core.RedisTemplate; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Mybatis - redis二级缓存 * * @author luohq * @date 2019/3/15 */ public final class MybatisRedisCache implements Cache { /** * 日志工具类 */ private static final Logger logger = LogManager.getLogger(MybatisRedisCache.class); /** * 读写锁 */ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * ID */ private String id; /** * 集成redisTemplate */ private static RedisTemplate redisTemplate; /** * Jackson-databind对象序列化器(核心) */ private static ObjectMapper objectMapper; /** * 序列化器初始化 */ static { objectMapper = new ObjectMapper(); //所有属性均可序列化 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); /** * 序列化类型信息(使用objectMapper.readValue(hashVal, Object.class)也可以实现相应类型的序列化和反序列化 * 好处:只定义一个序列化器就可以了(通用)) */ objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); } public MybatisRedisCache() { } public MybatisRedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } else { logger.info("MybatisRedisCache.id={}", id); this.id = id; } } @Override public String getId() { return this.id; } @Override public int getSize() { try { Long size = redisTemplate.opsForHash().size(this.id.toString()); logger.info("MybatisRedisCache.getSize: {}->{}", id, size); return size.intValue(); } catch (Exception e) { e.printStackTrace(); } return 0; } @Override public void putObject(final Object key, final Object value) { try { String hashVal = objectMapper.writeValueAsString(value); logger.info("MybatisRedisCache.putObject: {}->{}->{}", id, key, hashVal); redisTemplate.opsForHash().put(this.id.toString(), key.toString(), hashVal); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getObject(final Object key) { try { String hashVal = (String) redisTemplate.opsForHash().get(this.id.toString(), key.toString()); logger.info("MybatisRedisCache.getObject: {}->{}->{}", id, key, hashVal); if (null == hashVal) { return null; } return objectMapper.readValue(hashVal, Object.class); } catch (Exception e) { e.printStackTrace(); return null; } } @Override public Object removeObject(final Object key) { try { redisTemplate.opsForHash().delete(this.id.toString(), key.toString()); logger.info("MybatisRedisCache.removeObject: {}->{}->{}", id, key); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void clear() { try { redisTemplate.delete(this.id.toString()); logger.info("MybatisRedisCache.clear: {}", id); } catch (Exception e) { e.printStackTrace(); } } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } @Override public String toString() { return "MybatisRedisCache {" + this.id + "}"; } /** * 设置redisTemplate * * @param redisTemplate */ public void setRedisTemplate(RedisTemplate redisTemplate) { MybatisRedisCache.redisTemplate = redisTemplate; } }
改进如下:
(1)将序列化器直接配置进redisTemplate中,由redisTemplate来负责序列化;
代码如下:
import com.xxx.util.JsonUtils; import org.apache.ibatis.cache.Cache; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.data.redis.core.RedisTemplate; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Mybatis - redis二级缓存 * * @author luohq * @date 2019/3/15 */ public final class MybatisRedisCache implements Cache { /** * 日志工具类 */ private static final Logger logger = LogManager.getLogger(MybatisRedisCache.class); /** * 读写锁 */ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * ID */ private String id; /** * 集成redisTemplate */ private static RedisTemplate redisTemplate; public MybatisRedisCache() { } public MybatisRedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } else { logger.debug("MybatisRedisCache.id={}", id); this.id = id; } } @Override public String getId() { return this.id; } @Override public int getSize() { try { Long size = redisTemplate.opsForHash().size(this.id.toString()); logger.debug("MybatisRedisCache.getSize: {}->{}", id, size); return size.intValue(); } catch (Exception e) { e.printStackTrace(); } return 0; } @Override public void putObject(final Object key, final Object value) { try { logger.debug("MybatisRedisCache.putObject: {}->{}->{}", id, key, JsonUtils.toJson(value)); redisTemplate.opsForHash().put(this.id.toString(), key.toString(), value); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getObject(final Object key) { try { Object hashVal = redisTemplate.opsForHash().get(this.id.toString(), key.toString()); logger.debug("MybatisRedisCache.getObject: {}->{}->{}", id, key, JsonUtils.toJson(hashVal)); return hashVal; } catch (Exception e) { e.printStackTrace(); return null; } } @Override public Object removeObject(final Object key) { try { redisTemplate.opsForHash().delete(this.id.toString(), key.toString()); logger.debug("MybatisRedisCache.removeObject: {}->{}->{}", id, key); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void clear() { try { redisTemplate.delete(this.id.toString()); logger.debug("MybatisRedisCache.clear: {}", id); } catch (Exception e) { e.printStackTrace(); } } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } @Override public String toString() { return "MybatisRedisCache {" + this.id + "}"; } /** * 设置redisTemplate * * @param redisTemplate */ public void setRedisTemplate(RedisTemplate redisTemplate) { MybatisRedisCache.redisTemplate = redisTemplate; } }
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * Redis配置 * * @author luohq * @date: 2019/03/13 */ @Configuration public class RedisConfig { /** * 配置RedisTemplate * * @param factory * @return */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory, Jackson2JsonRedisSerializer redisJsonSerializer) { RedisTemplate
方式1:mybatis-config.xml
...
方式2:Springboot - application.properties
#使全局的映射器启用或禁用缓存。 mybatis.configuration.cache-enabled=true
......