这一节,我们一起来学习下mybatis中的缓存,mybatis一共存在两种缓存,一级缓存(默认开启),二级缓存(默认关闭),一级缓存为session级
别的缓存.二级缓存跟随命名空间的,
使用原生的DefaultSqlSession下,一级缓存会导致线程不安全,导致查询转换异常,通过SqlSessionManager和SqlSessionTemplete拦截代
理的方式解决,二级缓存为甚吗默认关闭那,二级缓存存在的问题是,假如存在用户表和用户详情表我们在用户表查询数据,再次查询的话,
如果二级缓存不存在,会放入到二级缓存,再次查询,直接返回,否则,直接从二级缓存返回,但是这个时候,我们修改了用户详情表,再次查询
用户的时候,会将原来的数据返回,并不会删除缓存,就会导致脏读,鉴于这个问题的存在,mybatis的二级缓存是直接关闭的,那意味着,是不
是我们就不能使用二级缓存了,不是的,只要我们将相关的操作,放入到一个命名空间下,还是可以使用二级缓存的,在mybatis源码包下的
cache中,我们可以看到mybatis定义了不同的缓存类,先进先出缓存,lru算法缓存,等等,代码都比较简单,就不做具体的分析了,我们今天
主要介绍下,关于在项目中集成其他的第三方缓存,1:redis,2:Ehcache,3:ignite
1redis我就不做具体的介绍了,非常典型的no-sql数据库(内存数据库,读写速度飞起)
接下来我们通过代码一起来看下mybatis集成redis缓存组委二级缓存
第一步:引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
#redis
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=xxxx
# Redis服务器连接端口
spring.redis.port=xxxx
# Redis服务器连接密码(默认为空)
spring.redis.password=xxxxx
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=2000ms
第二步:配置redis序列化方式
@Configuration
public class RedisConfig {
@Inject
private RedisConnectionFactory factory;
/**
* JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
* redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
* 设置键(key)的序列化采用StringRedisSerializer。
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置值(value)的序列化采用FastJsonRedisSerializer。
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
第三步:创建redisContext类
@Component
public class RedisContext {
private static RedisContext redisContext;
@Autowired
private RedisConfig redisConfig;
@PostConstruct
public void init(){
redisContext = this;
redisContext.redisConfig = this.redisConfig;
}
public static RedisTemplate getBean(){
return redisContext.redisConfig.redisTemplate();
}
}
第四步:
/**
* 二级缓存
*
* 注意:
* 1:org.apache.ibatis.cache.CacheKey cannot be cast to java.lang.String
* 需要将key转为String类型
*
* 2:当执行缓存的时候,如果手动修改数据库数据,会发生脏读
*
*/
public class RedisCache implements Cache {
private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
/**
* 缓存对象唯一标识
*/
private final String id;
/**
* 用于事务性缓存操作的读写锁
*/
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 缓存对象的是失效时间,60分钟
*/
private static final long EXPRIRE_TIME_IN_MINUT = 60;
/**
* 生成key
*/
private final String COMMON_CACHE_KEY = "com:ryx:";
/**
* 操作数据缓存的--跟着线程走的
* Redis的模板负责将缓存对象写到redis服务器里面去
*/
@Autowired
private RedisTemplate redisTemplate;
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
/**
* 所有key
*/
private String getKeys() {
return COMMON_CACHE_KEY + this.id + ":*";
}
/**
* 按照一定规则标识key
*/
private Object getKey(Object key) {
return COMMON_CACHE_KEY + this.id + ":" + DigestUtils.md5Hex(String.valueOf(key));
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
ValueOperations opsForValue = redisTemplate.opsForValue();
opsForValue.set(key.toString(), value, EXPRIRE_TIME_IN_MINUT, TimeUnit.MINUTES);
logger.debug("Put query result to redis");
}
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
logger.debug("Get cached query result from redis");
return opsForValue.get(key.toString());
}
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key.toString());
logger.debug("Remove cached query result from redis");
return null;
}
/**
* 处理redis清空所有缓存的问题
*/
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
RedisConnection connection = null;
try {
connection = redisTemplate.getConnectionFactory().getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
Long length = connection.lLen(serializer.serialize(id));
if (0 == length) {
return;
}
List<byte[]> keys = connection.lRange(serializer.serialize(id),0,length-1);
for (byte[] key :keys) {
connection.expireAt(key,0);
logger.info("删除缓存:"+serializer.deserialize(key).toString());
}
connection.expireAt(serializer.serialize(id),0);
keys.clear();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = RedisContext.getBean();
}
return redisTemplate;
}
}
最后一步:在mybatis的xml配置文件中开启缓存即可,
<cache type="com.serviceproduct.cache.redis.RedisCache"
eviction="LRU"
flushInterval="6000000"
size="1024"
readOnly="false"/>
二:Ehcache作为二级缓存
第一步:引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
第二步:在配置文件中添加Ehcache
#ehcache
#缓存的技术类型,可选 generic,ehcache,hazelcast,infinispan,jcache,redis,guava,simple,none
spring.cache.type=ehcache
# #应用程序启动创建缓存的名称,必须将所有注释为@Cacheable缓存name(或value)罗列在这里,
# 否者:Cannot find cache named 'xxx' for Builder[xx] caches=[sysItem] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
spring.cache.cache-names=default,users
#以下根据不同缓存技术选择配置
#EHCache的配置文件位置,不用配置,自动扫描
spring.cache.ehcache.config=classpath:/ehcache.xml
第三步:添加ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<!-- 默认的管理策略
maxElementsOnDisk: 在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔。标识对象状态(过期/持久化)的线程多长时间运行一次。
-->
<!--defaultCache:echcache的默认缓存策略 -->
<defaultCache name="default"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
<!-- 组播方式:multicastGroupPort需要保证与其他系统不重复,进行端口注册 -->
<!-- 若因未注册,配置了重复端口,造成权限缓存数据异常,请自行解决 -->
<!--
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic,
multicastGroupAddress=230.0.0.1,
multicastGroupPort=4546, timeToLive=1"/>-->
<!-- replicatePuts=true | false – 当一个新元素增加到缓存中的时候是否要复制到其他的peers. 默认是true。 -->
<!-- replicateUpdates=true | false – 当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。 -->
<!-- replicateRemovals= true | false – 当元素移除的时候是否进行复制。默认是true。 -->
<!-- replicateAsynchronously=true | false – 复制方式是异步的(指定为true时)还是同步的(指定为false时)。默认是true。 -->
<!-- replicatePutsViaCopy=true | false – 当一个新增元素被拷贝到其他的cache中时是否进行复制指定为true时为复制,默认是true。 -->
<!-- replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制(指定为true时为复制),默认是true。 -->
</ehcache>
怎嘛使用ehcache那更简单,
/**
* 根据primaryKey获取对象
*/
@Override
@Cacheable(value = "users",key = "#id")
public RyxAccount getRyxAccountByPrimaryKey(Integer id){
System.out.println("查库....");
return this.ryxAccountMapper.selectByPrimaryKey(id);
}
这部分内容网上很多,我就不具体分析了,大家看网上的资料即可哈!
Thanks!