使用Redis做MyBatis的二级缓存
Mybatis的缓存
通大多数ORM层框架一样,Mybatis自然也提供了对一级缓存和二级缓存的支持。一下是一级缓存和二级缓存的作用于和定义。
1、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
2、一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
一般的我们将Mybatis和Spring整合时,mybatis-spring包会自动分装sqlSession,而Spring通过动态代理sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(),执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的。
因此我们一般在项目中实现Mybatis的二级缓存,虽然Mybatis自带二级缓存功能,但是如果实在集群环境下,使用自带的二级缓存只是针对单个的节点,所以我们采用分布式的二级缓存功能。一般的缓存NoSql数据库如redis,Mancache等,或者EhCache都可以实现,从而更好地服务tomcat集群中ORM的查询。
使用Redis做MyBatis的二级缓存
1. 介绍
使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验。
使用redis做mybatis的二级缓存可是内存可控<如将单独的服务器部署出来用于二级缓存>,管理方便。
2. 使用思路
2.1 配置redis.xml 设置redis服务连接各参数
2.1 在配置文件中使用 <setting> 标签,设置开启二级缓存;
2.2 在mapper.xml 中使用<cache type="at.yuxin.com.util.MybatisRedisCache
"
/> 将cache映射到指定的MybatisRedisCache类中;
2.3 映射类RedisCacheClass 实现 MyBatis包中的Cache类,并重写其中各方法;
在重写各方法体中,使用redisFactory和redis服务建立连接,将缓存的数据加载到指定的redis内存中(putObject方法)或将redis服务中的数据从缓存中读取出来(getObject方法);
在redis服务中写入和加载数据时需要借用spring-data-redis.jar中JdkSerializationRedisSerializer.class中的序列化(serialize)和反序列化方法(deserialize),此为包中封装的redis默认的序列化方法;
2.4 映射类中的各方法重写完成后即可实现mybatis数据二级缓存到redis服务中;
3. 代码实践
3.1 配置spring-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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/cachehttp://www.springframework.org/schema/cache/spring-cache-4.2.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 启动缓存注解,这个必须的,否则注解不生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
<!-- <cache:annotation-driven cache-manager="cacheManager"/> -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="blockWhenExhausted" value="true" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="true" />
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.hostName}" />
<!-- <property name="hostName" value="192.168.1.105" />-->
<property name="port" value="${redis.port}"/>
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="usePool" value="true"/>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<!-- 开启事务支持 -->
<property name="enableTransactionSupport" value="true"/>
</bean>
<bean class="at.yuxin.com.util.MybatisRedisCacheTransfer">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
3.2 mybatisCfg.xml 配置开启二级缓存
<<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true" />
<!--开启延时加载,如果有关联关系,则默认不会获取数据 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
在association中指定fetchType="eager(立即)" 或者 lazy(延迟) 默认:false -->
<setting name="lazyLoadingEnabled" value="false" />
<!--true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载; false,每种属性将会按需加载。 默认为:true -->
<setting name="aggressiveLazyLoading" value="true" />
</settings>
</configuration>
3.3 在mapper.xml中映射缓存类MybatisRedisCacheClass
<?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="at.yuxin.com.dao.mapper.CompanyMapper" >
<resultMap id="BaseResultMap" type="at.yuxin.com.dao.model.Company" >
<id column="company_id" property="companyId" jdbcType="INTEGER" />
<result column="company_name" property="companyName" jdbcType="VARCHAR" />
<result column="company_addr" property="companyAddr" jdbcType="VARCHAR" />
<result column="company_legal_person" property="companyLegalPerson" jdbcType="VARCHAR" />
</resultMap>
<!--
flushInterval(清空缓存的时间间隔): 单位毫秒,可以被设置为任意的正整数。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目): 可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读):属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。
因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
eviction(回收策略): 默认的是 LRU:
1.LRU – 最近最少使用的:移除最长时间不被使用的对象。
2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
blocking(是否使用阻塞缓存): 默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思,
使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,
这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。
type(缓存类):可指定使用的缓存类,mybatis默认使用HashMap进行缓存
-->
<cache eviction="LRU" type="at.yuxin.com.util.MybatisRedisCache" flushInterval="5000"/>
<sql id="Base_Column_List" >
company_id, company_name, company_addr, company_legal_person
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" useCache="true">
select
<include refid="Base_Column_List" />
from company
where company_id =
#{companyId,jdbcType=INTEGER}
</select>
</mapper>
3.4 实现Mybatis中的Cache接口
Cache.class源码:
/*
* Copyright 2009-2012 the original author or authors.
import
java.util.concurrent.locks.ReadWriteLock;
public
interface
Cache {
String getId();
int
getSize();
void
putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void
clear();
ReadWriteLock getReadWriteLock();
}
MybatisRedisCache.java
packageat.yuxin.com.util;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.log4j.Logger;
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 org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
public class MybatisRedisCache implements Cache{
private static final Logger LOG = Logger.getLogger(MybatisRedisCache.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private static RedisTemplate<Serializable, Serializable> redisTemplate;
private String id;
private JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
public MybatisRedisCache(final String id){
if(id == null){
throw new IllegalArgumentException("Cache instances require an ID");
}
LOG.info("Redis Cache id " + id);
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if(value != null){
redisTemplate.opsForValue().set(key.toString(), jdkSerializer.serialize(value), 2, TimeUnit.DAYS);
}
}
@Override
public Object getObject(Object key) {
try {
if(key != null){
Object obj = redisTemplate.opsForValue().get(key.toString());
return jdkSerializer.deserialize((byte[])obj);
}
} catch (Exception e) {
LOG.error("redis ");
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if(key != null){
redisTemplate.expire(key.toString(), 1, TimeUnit.SECONDS);
}
} catch (Exception e) {
}
return null;
}
@Override
public void clear() {
//jedis nonsupport
}
@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();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
public static void setRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.redisTemplate = redisTemplate;
}
}
4. 总结
通过重写Cache类中的方法,将mybatis中默认的缓存空间映射到redis空间中。