一。MyBatis一级二级缓存
一级缓存:
MyBatis一级缓存为SqlSession级别的缓存,默认开启,相同的SqlSession对象查询相同条件的结果时,如果存在一级缓存,那么只会访问数据库一次,一级缓存在sqlSession关闭后失效,调用cleanCache后会被清除,执行过增删改后缓存也会被清除。注意:一级缓存不能跨session
以一个根据Student Id查询Student为例:
@Test
public void query1() throws IOException{
SqlSession session = getSession();
// 获取映射接口实例
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student stu1_1 = mapper.queryStudentById("1");
Student stu1_2 = mapper.queryStudentById("1");
System.out.println(stu1_1 == stu1_2);
}
输出结果:
还可以看出SQL语句只执行了一次,这就是MyBatis的一级缓存。记住这个概念:
同一个SqlSession查询相同条件的结果时,存在一级缓存只会查询一次。第一次查询
获取到数据后会通过session设置到一级缓存中,第二次查询时通过session一级缓存
判断是否存在相同主键的数据,存在则直接返回引用,否则查询数据库。
二级缓存:
二级缓存为SqlSessionFactory级别的缓存,默认不开启,要开启的话走下面这个步骤:
1.首先要在核心配置文件中配置,这里要注意顺序,要写在properties声明后:
MyBatis官方文档上的说明:
也就是说默认是打开的,直接用就OK了。不过这个开关只能控制二级缓存,一级缓存是不受影响的。
2.到需要使用二级缓存的namespace中定义:
属性详解:
eviction:回收策略,默认使用LRU算法。
也可以自己改,这里就改成了FIFO(队列),会把旧的数据清除掉,最先加入的数据最先
清除,不过这么做是不行的,如果最先加入的数据是热点数据,访问量大,清除掉了又要
重新去数据库中拉取,所以又有了LRU算法。
官方文档上的解释:
可用的收回策略有:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱
引用规则的对象。
默认的是 LRU。
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
测试方法:
@Test
public void cacheLevel2() throws IOException {
// 获取工厂
SqlSessionFactory factory = getFactory();
// 从同一个工厂中获取session
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
Student stu1 = mapper1.queryStudentById("1");
session1.close();
Student stu2 = mapper2.queryStudentById("1");
System.out.println(stu1 == stu2);
}
注意:如果不把session关掉,查询结果是不会设置到缓存中的,一个缓存一次只允许一个会话操作。因为考虑到如果两个会话同时操作一份缓存,会引起线程并发修改的问题,所以要关掉最先查询的那个session,查询结果才会被写到二级缓存中。
运行结果:
二。集成Redis
上面提到的FIFO LRU之类的缓存策略默认都有实现类来支持他们,这些策略都是实现自
Cache接口的,我们要通过MyBatis集成Redis实现自己的缓存策略,也是自定义一个实现类
来实现。不多BB,先上代码。
准备一个JavaBean:
实现Serializable的原因是我打算将Bean序列化成字节数组缓存到Redis中,取出来之后再反序列化成实体类 。既然要序列化和反序列化,还需要准备两个方法:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableTools {
/**
* 反序列化
*
* @param bt
* @return
* @throws IOException
* @throws Exception
*/
public static Object byteArrayToObj(byte[] bt) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bt);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
/**
* 对象序列化
*
* @param obj
* @return
* @throws IOException
*/
public static byte[] ObjToByteArray(Object obj) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
return bos.toByteArray();
}
}
OK,现在再定义个缓存策略实现类,实现Cache接口,并重写该类的一系列方法:
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
public class RedisCache implements Cache {
// 初始化Jedis
private Jedis jedis = new Jedis("127.0.0.1", 6379);
/*
* MyBatis会把映射文件的命名空间作为
* 唯一标识cacheId,标识这个缓存策略属于哪个namespace
* 这里定义好,并提供一个构造器,初始化这个cacheId即可
*/
private String cacheId;
public RedisCache (String cacheId){
this.cacheId = cacheId;
}
/**
* 清空缓存
*/
@Override
public void clear() {
// 但这方法不建议实现
}
@Override
public String getId() {
return cacheId;
}
/**
* MyBatis会自动调用这个方法检测缓存
* 中是否存在该对象。既然是自己实现的缓存
* ,那么当然是到Redis中找了。
*/
@Override
public Object getObject(Object arg0) {
// arg0 在这里是键
try {
byte [] bt = jedis.get(SerializableTools.ObjToByteArray(arg0));
if (bt == null) { // 如果没有这个对象,直接返回null
return null;
}
return SerializableTools.byteArrayToObj(bt);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public ReadWriteLock getReadWriteLock() {
return new ReentrantReadWriteLock();
}
@Override
public int getSize() {
return Integer.parseInt(Long.toString(jedis.dbSize()));
}
/**
* MyBatis在读取数据时,会自动调用此方法
* 将数据设置到缓存中。这里就写入Redis
*/
@Override
public void putObject(Object arg0, Object arg1) {
/*
* arg0是key , arg1是值
* MyBatis会把查询条件当做键,查询结果当做值。
*/
try {
jedis.set(SerializableTools.ObjToByteArray(arg0), SerializableTools.ObjToByteArray(arg1));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* MyBatis缓存策略会自动检测内存的大小,由此
* 决定是否删除缓存中的某些数据
*/
@Override
public Object removeObject(Object arg0) {
Object object = getObject(arg0);
try {
jedis.del(SerializableTools.ObjToByteArray(arg0));
} catch (IOException e) {
e.printStackTrace();
}
return object;
}
}
最后在配置文件中写上缓存实现类的全类名
以及在需要使用该缓存类的SQL语句上声明:
至此,自实现的二级缓存就可以用了。