一级缓存
MyBatis对缓存提供支持,但是在没有配置的默认的情况下,它只开启一级缓存(一级缓存只是相对于同一个SqlSession而言)。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用同一个Mapper的方法,往往只执行一次SQL,因为使用SqlSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession都只会取出当前缓存的数据,而不会再次发送SQL到数据库。
但是如果你使用的是不同的SqlSesion对象,因为不同的SqlSession都是相互隔离的,所以即使用相同的Mapper、参数和方法,它还是会再次发送SQL到数据库去执行,返回结果。
SqlSessionFactoryUtil
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
/**
* @author: Mitter
* @date: 2020-06-21 21:13
*/
public class SqlSessionFactoryUtil {
/**
* SqlSessionFactory对象
*/
private static SqlSessionFactory sqlSessionFactory = null;
/**
* 类线程锁
*/
private static final Class CLASS_LOCK = SqlSessionFactoryUtil.class;
/**
* 私有化构造参数,不让实例化,只能通过类来访问
* 避免使用者使用new的方式去创建多个对象
*/
private SqlSessionFactoryUtil() {}
/**
* 构建SqlSessionFactory,单利模式,懒汉式
* 某一个对象在应用中承担唯一职责的时候就是用单利模式,在本例中,SqlSessionFactory的唯一职责就是创建SqlSession
*/
private static void initSqlSessionFactory() {
String resource = "mybatis_config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (Exception e) {
e.printStackTrace();
}
// 静态方法使用类锁
synchronized (CLASS_LOCK) {
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
}
}
/**
* 打开SqlSession
* @return SqlSession
*/
public static SqlSession openSqlSession() {
if (sqlSessionFactory == null) {
initSqlSessionFactory();
}
return sqlSessionFactory.openSession();
}
}
测试观察一级缓存
观察日志发现第一个SqlSession实际只发生过一次查询,而第二次查询就从缓存中取出了,也就是SqlSession层面的一级缓存。
/**
* @author: Doooook
* @date: 2020-07-19 09:38
*/
public class MyBatisTest {
public static void main(String[] args) {
testMyBatisCache();
}
private static void testMyBatisCache() {
SqlSession sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper1 = sqlSession.getMapper(StudentMapper.class);
// 使用同一个sqlSession再执行一次
Student student1 = studentMapper1.selectByPrimaryKey(1);
Student student2 = studentMapper1.selectByPrimaryKey(1);
System.out.println("over!!!");
}
}

二级缓存
为了客服一级缓存SqlSession间相互隔离的问题,我们往往需要配置二级缓存,使得缓存在SqlSessionFactory层面上能够提供给各个SqlSession对象共享。
而SqlSessionFactory层面上的二级缓存是不开启的,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,配置的方法很简单,只需要在映射XML文件配置就可以开启缓存了,在响应的方法上加上<cache/>标签即可。
<cache/>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer">
select
<include refid="Base_Column_List" />
from t_student
where id = #{id,jdbcType=INTEGER}
</select>
这样的一个语句里面,很多设置是默认的,如果我们只是这样配置,那么就意味着:
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所有insert、update和delete语句会刷新缓存。
- 缓存会使用默认的LeastRecentlyUsed(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如NoFlushInterval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用。
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,不干扰其他调用者或线程所做的潜在修改。
测试观察二级缓存
同一个SqlSessionFactory下的不同SqlSession使用了二级缓存,只发送了一次SQL查询
private static void testMyBatisSndCache() {
SqlSession sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setCnname("陈六");
student.setSex(Byte.valueOf("1"));
student.setSelfcardNo(100034);
student.setNote("");
student.setStatus(Byte.valueOf("0"));
// 执行了更新操作(delete、update、insert)后,缓存就会被清空,下面的查询就会再次发送SQL查询数据库
int insert = studentMapper.insert(student);
// 请注意,当使用二级缓存的时候,sqlSession调用了commit方法后才会生效
// 只有commit以后才会清楚缓存
sqlSession.commit();
SqlSession sqlSession2 = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
Student student2 = studentMapper2.selectByPrimaryKey(1);
// 使用同一个SqlSessionFactory下的不同SqlSession
SqlSession sqlSession3 = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
Student student22 = studentMapper2.selectByPrimaryKey(1);
// 请注意,当使用二级缓存的时候,sqlSession调用了commit方法后才会生效
sqlSession2.commit();
}

网友评论