Mybatis提供了一级缓存和二级缓存。
一级缓存:一级缓存的存储作用域为一个SqlSession会话,当SqlSession会话被flush或close之后,该session会话中的所有catch数据就会被清空。
二级缓存:二级缓存与一级缓存的实现机制相同,但其作用域为一个mapper文件(即一个namespace空间中的sql语句)。
不管一级缓存还是二级缓存,当某一个作用域执行了C/U/D(增、改、删)时,默认作用域下的缓存就会被清空。所以缓存一般用于查询语句。
示例如下:通过用户的id查询用户的信息。
一、一级缓存
sql的userMapper.xml映射文件为:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lzj.mybaits.test1.userMapper">
<select id="getUser" parameterType="int" resultType="User">
select * from users where id=#{id}
</select>
<update id="updUser" parameterType="User">
update users set name=#{name}, age=#{age} where id=#{id}
</update>
</mapper>
Mybatis的配置文件为:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.lzj.mybaits.test1" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- 配置数据源 -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/lzj_database" />
<property name="username" value="root" />
<property name="password" value="lzjlzj" />
</dataSource>
</environment>
</environments>
<!-- 指定sql的映射文件,如果有多个,都可以加进来 -->
<mappers>
<mapper resource="com/lzj/mybaits/test1/userMapper.xml" />
</mappers>
</configuration>
测试一级缓存:
package com.lzj.mybaits.test1;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
public class MybaitsTest {
public SqlSessionFactory getFactory(){
String resource = "conf.xml";
InputStream in = MybaitsTest.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
return factory;
}
/*测试一级缓存*/
@Test
public void testCatchOne(){
SqlSession session = getFactory().openSession();
String statement = "com.lzj.mybaits.test1.userMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println(user);
/*此时不会去重新读数据库,而是从一级缓存中读数据*/
user = session.selectOne(statement, 1);
System.out.println(user);
/*清除缓存中数据*/
session.clearCache();
/*缓存中数据已经被清除了,需要重新读数据库拿数据*/
User user2 = session.selectOne(statement, 1);
System.out.println(user2);
/*缓存只是针对select查询语句,使用增、删、改语句会清除缓存*/
int updNum = session.update(statement, new User(1, "Jerry", 25));
session.commit();
System.out.println("updNum=" + updNum);
/*上面修改语句会清除缓存,所以需要重新读数据库拿数据*/
user2 = session.selectOne(statement, 1);
System.out.println(user2);
/*得到的是另一个新的session,不会用上面上面session的缓存,所以会重新读数据库*/
SqlSession session2 = getFactory().openSession();
user2 = session2.selectOne(statement, 1);
System.out.println(user2);
/*session2对应的session缓存中没有要的数据,此时也要重新读数据库*/
User user3 = session2.selectOne(statement, 2);
System.out.println(user3);
session.close();
}
}
二、二级缓存
要实现二级缓存,只需要在userMapper.xml映射文件中加入<catch>
就可以了。userMapper.xml文件修改如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lzj.mybaits.test1.userMapper">
<cache></cache>
<select id="getUser" parameterType="int" resultType="User">
select * from users where id=#{id}
</select>
<update id="updUser" parameterType="User">
update users set name=#{name}, age=#{age} where id=#{id}
</update>
</mapper>
测试二级缓存:
package com.lzj.mybaits.test1;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
public class MybaitsTest {
public SqlSessionFactory getFactory(){
String resource = "conf.xml";
InputStream in = MybaitsTest.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
return factory;
}
/*测试二级缓存*/
public void testCatchTwo(){
SqlSession session = getFactory().openSession();
String statement = "com.lzj.mybaits.test1.userMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println(user);
/*由于两次sql语句虽然用的session不同,但调用的是同一个userMapper.xml映射文件中的sql语句,所以会从同一个二级缓存中读数据*/
SqlSession session2 = getFactory().openSession();
user = session2.selectOne(statement, 1);
System.out.println(user);
}
}
上面实现二级缓存时,只是在映射文件中加入<catch>
标签,当然还可以设置<catch>
标签的其它属性。
eviction="FIFO" //回收策略为先进先出
flushInterval="60000" //自动刷新时间60s
size="512" //最多缓存512个引用对象
readOnly="true"/> //只读
二级标签配置的属性如下:
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
<!--
eviction:缓存的回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
实现Cache接口即可;
-->
三、标签
当有多个mapper映射文件存在时,如果要设置二级缓存,需要在每个mapper的映射文件中开启二级缓存,可以在每个mapper的映射文件中设置<catch>
标签,此时默认每个映射文件都开启了二级缓存。如果要使每个映射文件都不开启二级缓存,可以在mybatis的配置文件中(本文中为conf.xml)如下配置
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
此时,即使每个mapper的映射文件中都开启了二级缓存,配置上面后,所有的二级缓存都会失效。如果设置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
表示所有的mapper的映射文件中设置<catch>
的映射文件可以使用二级缓存,没有设置<catch>
标签的mapper文件不可以使用二级缓存。
mybatis的配置文件中<setting name="cacheEnabled" value="true"/>
默认是设置true的。
总结
* 两级缓存:
* 一级缓存:(本地缓存):sqlSession级别的缓存。一级缓存是一直开启的;SqlSession级别的一个Map
* 与数据库同一次会话期间查询到的数据会放在本地缓存中。
* 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
*
* 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询):
* 1、sqlSession不同。
* 2、sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
* 3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
* 4、sqlSession相同,手动清除了一级缓存(缓存清空)
*
* 二级缓存:(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存:
* 工作机制:
* 1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
* 2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
* 3、
* 不同namespace查出的数据会放在自己对应的缓存中(map)
* 效果:数据会从二级缓存中获取
* 查出的数据都会被默认先放在一级缓存中。
* 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
* 使用:
* 1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
* 2)、去mapper.xml中配置使用二级缓存:
* <cache></cache>
* 3)、我们的POJO需要实现序列化接口
*
* 和缓存有关的设置/属性:
* 1)、cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)
* 2)、每个select标签都有useCache="true":
* false:不使用缓存(一级缓存依然使用,二级缓存不使用)
* 3)、【每个增删改标签的:flushCache="true":(一级二级都会清除)】
* 增删改执行完成后就会清楚缓存;
* 测试:flushCache="true":一级缓存就清空了;二级也会被清除;
* 查询标签:flushCache="false":
* 如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;
* 4)、sqlSession.clearCache();只是清楚当前session的一级缓存;
* 5)、localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中;
* STATEMENT:可以禁用一级缓存;