最近项目需要针对mybatis查询加入缓存,使用redis,于是上网查找mybatis缓存 redis实现的相关文章,有很多关于mybatis redis缓存的介绍以及mybatis Cache接口的redis实现代码,但内容都是一致,看得出都转载某一人的手笔,虽然提供的代码逻辑是正确的,但是在项目应用中都存在问题。请小心使用!
为什么网上流传的mybatis redis实现代码有问题,只要你调试过、测试过,你会发现有这么个问题存在:当页面对数据表存在CUD操作时,以往所有的查询缓存都会clear掉,当再次查询时,重新缓存;只有任意一个CUD操作,整个mybatis缓存都会清空,这样会带来相反的结果,不能利用缓存提供效率,反而降低了系统性能。我们的目标就是,针对Mybatis的Mapper.xml文件,当数据变动后,只清除变动语句存在mapper.xml里的查询缓存,而不是清除所有mapper.xml查询语句缓存。
想了解这块的人,可以对比本文实现代码,和网上其他文章的实现代码,虽然缓存都是可用,但是对系统性能却存在极大差别。
下面分享mybatis缓存 redis实现的步骤以及关键代码:
1,开启mybatis二级缓存,<prop key="cacheEnabled">true</prop> ,在sqlSessionFactory配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 配置sqlSessionFactory的参数 -->
<property name="configurationProperties">
<props>
<prop key="cacheEnabled">true</prop>
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<prop key="lazyLoadingEnabled">false</prop>
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指定),
不会加载关联表的所有字段,以提高性能 -->
<prop key="aggressiveLazyLoading">true</prop>
<!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
<prop key="multipleResultSetsEnabled">true</prop>
<!-- 允许使用列标签代替列名 -->
<prop key="useColumnLabel">true</prop>
<!-- 允许使用自定义的主键值(比如由程序生成的UUID 32位编码作为键值),
数据表的PK生成策略将被覆盖 -->
<prop key="useGeneratedKeys">true</prop>
<!-- 给予被嵌套的resultMap以字段-属性的映射支持 -->
<prop key="autoMappingBehavior">FULL</prop>
<!-- 对于批量更新操作缓存SQL以提高性能 -->
<prop key="defaultExecutorType">BATCH</prop>
<!-- 数据库超过25000秒仍未响应则超时 -->
<prop key="defaultStatementTimeout">25000</prop>
</props>
</property>
</bean>
2,在需要添加缓存的mybatis mapper.xml文件中指定缓存Cache实现类,广泛采用LRU算法:
<mapper namespace="com......Mapper">
<cache eviction="LRU" type="com.xfx.service.cache.redis.MybatisRedisCache" />
</mapper>
3,mybatis redis缓存重点,Cache实现类(通过jedisPool获取jedis的方式你可以修改):
MybatisRedisCache.java
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.ibatis.cache.Cache;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/*
* 使用第三方缓存服务器,处理二级缓存
* <a href="http://www.zyiqibook.com">在一起 学习交流分享网 功能源码分享</a>
* @author xfxpeter@gmail.com
*/
public class MybatisRedisCache implements Cache {
private static final Logger logger = Logger.getLogger(MybatisRedisCache.class);
/** The ReadWriteLock. */
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private JedisPool jedisPool;
private static final int DB_INDEX = 1;
private final String COMMON_CACHE_KEY = "COM:";
private static final String UTF_8 = "utf-8";
/**
* 按照一定规则标识key
*/
private String getKey(Object key) {
StringBuilder accum = new StringBuilder();
accum.append(COMMON_CACHE_KEY);
accum.append(this.id).append(":");
accum.append(DigestUtils.md5Hex(String.valueOf(key)));
return accum.toString();
}
/**
* redis key规则前缀
*/
private String getKeys() {
return COMMON_CACHE_KEY + this.id + ":*";
}
private String id;
private Properties properties;
{
properties = getProp();
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(Integer.valueOf(properties
.getProperty("redis.pool.maxIdle")));
jedisPool = new JedisPool(config, properties.getProperty("redis.host"),
Integer.valueOf(properties.getProperty("redis.port")),
Integer.valueOf(properties.getProperty("redis.timeout")),
properties.getProperty("redis.password"));
}
/**
* 加载项目redis连接属性文件
*/
private Properties getProp(){
if(properties == null || properties.isEmpty()){
String propName = "config.properties";
properties = new Properties();
InputStream is = null;
BufferedReader bf = null;
try {
is= this.getClass().getResourceAsStream("/META-INF/config.properties");//将地址加在到文件输入流中
bf = new BufferedReader(new InputStreamReader(is,"UTF-8"));//转为字符流,设置编码为UTF-8防止出现乱码
properties.load(bf);//properties对象加载文件输入流
} catch (UnsupportedEncodingException e) {
logger.error(propName + "编码格式转换失败,不支持指定编码。" + e);
} catch (FileNotFoundException e) {
logger.error(propName + "属性文件common.properties不存在。" + e);
} catch (IOException e) {
logger.error(propName + "属性文件common.properties读取失败。" + e);
} catch (Exception e) {
logger.error(propName + "属性文件common.properties读取失败。" + e);
} finally {
try {//文件流关闭
if(bf != null){
bf.close();
}
if(is != null ){
is.close();
}
} catch (IOException e) {
logger.error("关闭文件流失败。" + e);
}
}
}
return properties;
}
public MybatisRedisCache() {
}
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("必须传入ID");
}
logger.debug("MybatisRedisCache:id=" + id);
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public int getSize() {
Jedis jedis = null;
int result = 0;
boolean borrowOrOprSuccess = true;
try {
jedis = jedisPool.getResource();
jedis.select(DB_INDEX);
Set<byte[]> keys = jedis.keys(getKeys().getBytes(UTF_8));
if (null != keys && !keys.isEmpty()) {
result = keys.size();
}
logger.debug(this.id+"---->>>>总缓存数:" + result);
} catch (Exception e) {
borrowOrOprSuccess = false;
if (jedis != null)
jedisPool.returnBrokenResource(jedis);
} finally {
if (borrowOrOprSuccess)
jedisPool.returnResource(jedis);
}
return result;
}
@Override
public void putObject(Object key, Object value) {
Jedis jedis = null;
boolean borrowOrOprSuccess = true;
try {
jedis = jedisPool.getResource();
jedis.select(DB_INDEX);
byte[] keys = getKey(key).getBytes(UTF_8);
jedis.set(keys, SerializeUtil.serialize(value));
logger.debug("添加缓存--------"+this.id);
//getSize();
} catch (Exception e) {
borrowOrOprSuccess = false;
if (jedis != null)
jedisPool.returnBrokenResource(jedis);
} finally {
if (borrowOrOprSuccess)
jedisPool.returnResource(jedis);
}
}
@Override
public Object getObject(Object key) {
Jedis jedis = null;
Object value = null;
boolean borrowOrOprSuccess = true;
try {
jedis = jedisPool.getResource();
jedis.select(DB_INDEX);
value = SerializeUtil.unserialize(jedis.get(getKey(key).getBytes(UTF_8)));
logger.debug("从缓存中获取-----"+this.id);
//getSize();
} catch (Exception e) {
borrowOrOprSuccess = false;
if (jedis != null)
jedisPool.returnBrokenResource(jedis);
} finally {
if (borrowOrOprSuccess)
jedisPool.returnResource(jedis);
}
return value;
}
@Override
public Object removeObject(Object key) {
Jedis jedis = null;
Object value = null;
boolean borrowOrOprSuccess = true;
try {
jedis = jedisPool.getResource();
jedis.select(DB_INDEX);
value = jedis.del(getKey(key).getBytes(UTF_8));
logger.debug("LRU算法从缓存中移除-----"+this.id);
//getSize();
} catch (Exception e) {
borrowOrOprSuccess = false;
if (jedis != null)
jedisPool.returnBrokenResource(jedis);
} finally {
if (borrowOrOprSuccess)
jedisPool.returnResource(jedis);
}
return value;
}
@Override
public void clear() {
Jedis jedis = null;
boolean borrowOrOprSuccess = true;
try {
jedis = jedisPool.getResource();
jedis.select(DB_INDEX);
Set<byte[]> keys = jedis.keys(getKeys().getBytes(UTF_8));
logger.debug("出现CUD操作,清空对应Mapper缓存======>"+keys.size());
for (byte[] key : keys) {
jedis.del(key);
}
//下面是网上流传的方法,极大的降低系统性能,没起到加入缓存应有的作用,这是不可取的。
//jedis.flushDB();
//jedis.flushAll();
} catch (Exception e) {
borrowOrOprSuccess = false;
if (jedis != null)
jedisPool.returnBrokenResource(jedis);
} finally {
if (borrowOrOprSuccess)
jedisPool.returnResource(jedis);
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
SerializeUtil.java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 序列化与反序列化
* <a href="http://www.zyiqibook.com">在一起 学习交流分享网 功能源码分享</a>
* @author xfxpeter@gmail.com
*/
public class SerializeUtil {
public static byte[] serialize(Object object) {
if (null == object) return null;
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
//序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object unserialize(byte[] bytes) {
if(null == bytes) return null;
ByteArrayInputStream bais = null;
try {
//反序列化
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
redis缓存使用好了,对项目性能有极大改变,特别是大型web应用项目,这是大型项目性能优化的重点内容。