mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
mybaits提供一级缓存,和二级缓存。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
一、一级缓存
通过id查询学生信息
@Test
public void testcache(){
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student stu = mapper.getStuById(1);
System.out.println(stu);
Student stu1 = mapper.getStuById(1);
System.out.println(stu1);
}
打印的日志信息
第一次查询的时候发送一条SQL语句到数据库,第二次查询没有去发送SQL语句。
每次查询的时候都会去缓存里查看一下,如果命中缓存就直接从缓存中拿出数据,没有命中缓存就发送SQL语句到数据库进行查询
第一次查询的时候去查询缓存,缓存没有数据,发送SQL语句到数据看。
第二次命中缓存,直接从缓存中拿出数据。所以只发送一次SQL到数据库
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
@Test
public void testcache(){
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student stu = mapper.getStuById(3);
System.out.println(stu);
stu.setName("jerry");
mapper.updateStu(stu);
session.commit();
Student stu1 = mapper.getStuById(3);
System.out.println(stu1);
}
第一次查询数据后将数据放到缓存里面,然后执行更新操作后删除了缓存,所以第二次查询缓存中没有数据,发送了一条SQL语句到数据库
二、二级缓存
二级缓存默认是关闭的,需要手动的开启
1.全局配置文件
<setting name="cacheEnabled" value="true" />
2.Mpper.xml 文件
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly = "true"/>
元素 | 作用 |
---|---|
eviction | 缓存淘汰策略 |
flushInterval | 自动刷新时间 |
size | 缓存对象引用的数量 |
readOnly | 只读 |
eviction 元素有四种取值
- LRU:最近最少使用,优先数量长时间未使用的对象
- FIFO:先进先出 按照对象的进入缓存的顺序进行先进先淘汰
- SOFT:软引用
- WEAK:虚引用
@Test
public void testcache2() throws Exception{
Mybatisutils mybatisutils = new Mybatisutils();
SqlSession session,session1,session2,session3;
String resource = "mybatis-conf.xml";
//通过Resource 获取mybatis 流
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
System.out.println("第一次查询开始");
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student stu = mapper.getStuById(1);
session.close();
System.out.println("第一次查询结束");
System.out.println("第二次查询开始");
session1 = sqlSessionFactory.openSession();
StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
Student stu1 = mapper1.getStuById(1);
session1.close();
System.out.println("第二次查询结束");
session2 = sqlSessionFactory.openSession();
StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
stu1.setName("lihua");
mapper2.updateStu(stu1);
session2.commit();
System.out.println("第三次查询开始");
session3 = sqlSessionFactory.openSession();
StudentMapper mapper3 = session3.getMapper(StudentMapper.class);
Student stu3 = mapper3.getStuById(1);
System.out.println("第三次查询结束");
}
第一次查询完缓存里有了数据,第二次查询的时候就直接在缓存里面拿,然后进行了一个更新,更新操作会清空缓存的数据(为了避免拿到脏数据),所以第三次就没有命中缓存。
说一下我在这儿犯的错误:
刚开始我自己写了一个Mybatis 工具类,就是下面这段代码,返回了一个Sqlsession,getSession() 方法每次新建一个SqlSessionFactory 实例来创建SqlSession,所以二级缓存一直不能命中。
后来通过查阅资料,发现这写SqlSession 必须是由同一个SqlSessioonFactory 创建的,具体为什么我还没有找到原因,这个后面会更新下。
public SqlSession getsession() throws IOException {
String resource = "mybatis-conf.xml";
//通过Resource 获取mybatis 流
InputStream inputStream = Resources.getResourceAsStream(resource);
//获取SqlSessionFactory 实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//我们就可以从中获得 SqlSession 的实例了。
// SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
SqlSession session = sqlSessionFactory.openSession();
return session;
}
}
-
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
-
设置statement配置中的flushCache=‘true’ 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读
<select id="getStuById" resultType="com.tulun.bean.Student" flushCache="true">
select * from student where id = #{id}
</select>
- 在配置完二级缓存的基础上某个方法不用二级缓存,则以用useCache参数
<select id="getStuById" resultType="com.tulun.bean.Student" useCache="false">
select * from student where id = #{id}
</select>
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存默认情况下为true,我们不用去设置它,这样可以避免数据库脏读。
二级缓存的局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,对同时缓存较多条数据的缓存,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。需要使用三级缓存