前言
内存:临时存储数据的空间,断电后消失
硬盘:持久化的数据在硬盘当中。文件也存储在硬盘当中。关机然后重启硬盘中的文件还在。这是一个持久化的设备
缓存(cache):提前把数据放到内存中,下一次用的时候,直接从缓存中拿。效率高
实际上各大关系型数据库的数据,都是存储在文件当中
缓存通常是程序开发中优化程序的重要手段:字符串常量池、线程池、连接池、整数型常量池……
一、Mybatis缓存机制
执行DQL(Data Query Language)–select语句的时候,将查询到的结果存放在缓存(内存)当中,如果下一次还是执行同样的dql语句,直接从缓存中拿数据,不再从硬盘上找数据了。
目的:提高执行效率
缓存机制:使用减少IO的方式来提高效率
mybatis缓存包括:
一级缓存:将查询到的数据存储到SqlSession当中
二级缓存:将查询到的数据存储到SqlSessionFactory当中
或者集成其他第三方的缓存
补充
SqlSessionFactory的生命周期:一个数据库一个SqlSessionFactory
SqlSession的生命周期:当前的SQL会话
缓存只针对于DQL语句,也就是说缓存机制只对应select语句
二、一级缓存
一级缓存默认是开启的,不需要做任何配置。
原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存
代码如下: 根据id查找对应的汽车信息
接口:
public interface CarMapper {
/**
* 根据id获取Car信息
* @param id
* @return
*/
Car selectById(Long id);
}
CarMapper.xml
<select id="selectById" resultType="car">
select * from t_car
where id=#{id}
</select>
测试:
@Test
public void testCcarMapper() throws IOException {
SqlSession sqlsession = SqlSessionUtil.openSession();
CarMapper carMapper = sqlsession.getMapper(CarMapper.class);
Car car = carMapper.selectById(1L);
System.out.println(car);
sqlsession.close();
}
查询正常。
现在进行缓存测试,在测试类中再进行一次查找返回查找信息为car1
@Test
public void testCcarMapper() throws IOException {
SqlSession sqlsession = SqlSessionUtil.openSession();
CarMapper carMapper = sqlsession.getMapper(CarMapper.class);
Car car = carMapper.selectById(1L);
System.out.println(car);
Car car1 = carMapper.selectById(1L);
System.out.println(car1);
sqlsession.close();
}
根据日志信息提示只进行了一次SQL语句查询,是由于同一个mapper变量的原因?
再进行测试:创建一个变量carMapper2 ,让carMapper2 去执行第二条SQL语句
@Test
public void testCcarMapper() throws IOException {
SqlSession sqlsession = SqlSessionUtil.openSession();
CarMapper carMapper = sqlsession.getMapper(CarMapper.class);
Car car = carMapper.selectById(1L);
System.out.println(car);
CarMapper carMapper2 = sqlsession.getMapper(CarMapper.class);
Car car1 = carMapper2.selectById(1L);
System.out.println(car1);
sqlsession.close();
}
两个carMapper 都是通过一个sqlsession对象获取的
依然还是只进行了一次SQL语句查询
一级缓存存放在sqlSession中,那我们创建两个 sqlSession对象再去验证,
答案是可以的。但是,在之前工具类中,使用了ThreadLocal
SqlSessionUtil工具类:
package com.powernode.util;
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.IOException;
/**
* Mybatis工具类
* @author bwy
*/
public class SqlSessionUtil {
static SqlSessionFactory factory = null;
private SqlSessionUtil(){}
static{
try {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
//全局的服务器级别的 一个服务器当中定义一个即可,为了保证一个线程对应一个session
//为什么把session对象放在ThreadLocal当中
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 获取会话对象
* @return
* @throws IOException
*/
public static SqlSession openSession() throws IOException {
SqlSession session = local.get();
if (session==null) {
session = factory.openSession();
//绑定 将session对象绑定到当前线程上
local.set(session);
}
return session;
}
/**
* 关闭session对象 (从当前线程中移除Session对象)
* @param session
*/
public static void close(SqlSession session){
if (session!=null) {
session.close();
//注意移除session对象和当前线程的绑定关系
//Tomcat服务器支持线程池, 也就是说用过的线程t1,可能下一次还会使用这个线程
local.remove();
}
}
}
因此,如果要获取两个sqlSession对象测试,就不能再使用工具类
创建一个SqlSessionFactory可以创建多个SqlSession对象
public void testCcarMapper2() throws IOException {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session1 = factory.openSession();
CarMapper carMapper = session1.getMapper(CarMapper.class);
Car car = carMapper.selectById(1L);
System.out.println(car);
SqlSession session2 = factory.openSession();
CarMapper carMapper2 = session2.getMapper(CarMapper.class);
Car car1 = carMapper2.selectById(1L);
System.out.println(car1);
session1.close();
session2.close();
}
执行了两次SQL语句,获取了两个SqlSesion就是两个缓存。
思考:
1、什么时候不走缓存?
SqlSession对象不是同一个,肯定不走缓存
查询条件不一样肯定也不走缓存
2、什么时候一级缓存失效?
第一次DQL和第二次DQL之间做了以下两件事中的任意一件,都会让一级缓存清空:
其一:执行了sqlSession的clearCache()方法,这是手动清空缓存。
其二:执行了insert或delete或update语句,不管是操作哪张表的,都会清空一级缓存
不做演示了。
三、二级缓存
二级缓存的范围是:SqlSessionFactory。
使用二级缓存需要具备以下几个条件:
1、< setting name=“cacheEnabled” value=“true”>全局性的开启或关闭所有映射器配置文件中已配置的任何缓存,默认就是true,无需设置
2、在使用二级缓存的SqlMapper.xml文件中添加配置:< cache/>
3、使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
4、SqlSession对象关闭或提交后,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用
在CarMapper.xml文件中添加标签
<!--
默认情况下二级缓存是开启的
只需要在对应的SqlMapper.xml文件中添加以下标签,用来表示使用该二级缓存
-->
<cache></cache>
实体类实现java.io.Serializable接口
public class Car implements Serializable{
……
}
接口:
/**
* 根据id获取Car信息
* 测试二级缓存
* @param id
* @return
*/
Car selectById2(Long id);
CarMapper.xml
<!--
默认情况下二级缓存是开启的
只需要在对应的SqlMapper.xml文件中添加以下标签,用来表示使用该二级缓存
-->
<cache></cache>
<select id="selectById2" resultType="car">
select * from t_car
where id=#{id}
</select>
测试:
此时测试类中将数据放入两个一级缓存,当session1 session2 执行close之后数据才会被放入二级缓存当中。
@Test
public void testCcarMapper3() throws IOException {
//这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
CarMapper carMapper = session1.getMapper(CarMapper.class);
CarMapper carMapper2 = session2.getMapper(CarMapper.class);
//这行结束之后,实际上数据是缓存到一级缓存当中了(session1)
Car car = carMapper.selectById2(1L);
System.out.println(car);
//如果这里不关闭SqlSession对象的话,二级缓存中是没有数据的。
//这行结束之后,实际上数据是缓存到一级缓存当中了(session2) 两个一级缓存不一样
Car car1 = carMapper2.selectById2(1L);
System.out.println(car1);
//程序执行到这里会将session1这个一级缓存中的数据写入到二级缓存当中
session1.close();
//程序执行到这里会将session2这个一级缓存中的数据写入到二级缓存当中
session2.close();
}
应该会调用二次SQL语句:
执行结果:
调整一下session1关闭的位置。
@Test
public void testCcarMapper3() throws IOException {
//这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
CarMapper carMapper = session1.getMapper(CarMapper.class);
CarMapper carMapper2 = session2.getMapper(CarMapper.class);
//这行结束之后,实际上数据是缓存到一级缓存当中了(session1)
Car car = carMapper.selectById2(1L);
System.out.println(car);
//如果这里不关闭SqlSession对象的话,二级缓存中是没有数据的。
//程序执行到这里会将session1这个一级缓存中的数据写入到二级缓存当中
session1.close();
//这行结束之后,实际上数据是缓存到一级缓存当中了(session2) 两个一级缓存不一样
Car car1 = carMapper2.selectById2(1L);
System.out.println(car1);
//程序执行到这里会将session2这个一级缓存中的数据写入到二级缓存当中
session2.close();
}
执行结果:
session1结束之后,执行了close将数据放在二级缓存当中。因此session2执行时直接去二级缓存中拿数据,不会再执行SQL语句。
只执行了一次SQL语句。
二级缓存什么时候失效?
只要两次查询之间出现了增删改操作,二级缓存就会失效【一级缓存也会失效】
二级缓存的一些设置
四、Mybatis集成EhCache
集成EhCache是为了代替mybatis自带的二级缓存,一级缓存是无法替代的。