【MyBatis】Mybatis 缓存
缓存的重要性是不言而喻的。 使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。
mybatis 也提供了对缓存的支持, 分为一级缓存和二级缓存。 但是在默认的情况下, 只开启一级缓存(一级缓存是对同一个 SqlSession 而言的)。
1. 一级缓存
1. 同一个SqlSession情况下
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirLevelCache(){
User user1 = iuSerDao.findById(41);
System.out.println(user1);
User user2 = iuSerDao.findById(41);
System.out.println(user2);
System.out.println(user1==user2);
}
}
输出结果为:
很容易看出 在同一个SqlSession对象的情况下,只执行一次 SQL 语句。
并且得到的对象是同一个。
2. 不同SqlSession情况下
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirLevelCache(){
User user1 = iuSerDao.findById(41);
System.out.println(user1);
session.close();
session = factory.openSession(true);
iuSerDao = session.getMapper(IUSerDao.class);
User user2 = iuSerDao.findById(41);
System.out.println(user2);
System.out.println(user1==user2);
}
}
输出结果如下:
由此我们可以看出两个不同的SqlSession对象会执行两次Sql语句,并且获得的对象不是同一个。
3. 刷新缓存
刷新缓存是清空这个 SqlSession 的所有缓存。我们可以使用sqlSession.clearCache()方法。
或者设置映射配置文件中查询方法属性中添加flushCache=“true”
。
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirLevelCache(){
User user1 = iuSerDao.findById(41);
System.out.println(user1);
session.clearCache();//此方法也可以清空缓存
User user2 = iuSerDao.findById(41);
System.out.println(user2);
System.out.println(user1==user2);
}
}
运行结果如下:
由此我们可以看出缓存确实被清空了。
我们一直执行的都是查询方法,如果执行CUD方法呢?
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirLevelCache(){
User user1 = iuSerDao.findById(41);
System.out.println(user1);
iuSerDao.updateUser(user1);
User user2 = iuSerDao.findById(41);
System.out.println(user2);
System.out.println(user1==user2);
}
}
运行结果如下:
由此可以看出执行更新方法同样会刷新缓存。
当然除了更新其他的插入和删除方法同样会刷新缓存。
4. 一级缓存总结
-
在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
-
不同的 SqlSession 之间的缓存是相互隔离的;
-
用一个 SqlSession, 可以通过配置或者使用clearCache方法使得在查询前清空缓存;
-
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。
2. 二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。存在于SqlSessionFactory 生命周期中。
首先开启 mybatis 的二级缓存。
sqlSession 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
- 注意:当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
2.1 二级缓存的开启和关闭
- 第一步:在主配置文件开启二级缓存
<?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>
<!-- 配置properties -->
<properties resource="jdbcConfig.properties"></properties>
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.siyi.domain"></package>
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射配置文件 -->
<mappers>
<mapper resource="com/siyi/dao/IUserDao.xml"/>
</mappers>
</configuration>
- 第二步:配置相关的 Mapper 映射文件
<?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.siyi.dao.IUSerDao">
<!-- 开启user支持二级缓存 -->
<cache></cache>
</mapper>
- 第三步: 配置 statement 上面的 useCache 属性
<select id="findById" parameterType="Integer" resultType="User" useCache="true">
select * from user where id=#{id}
</select>
将 UserDao.xml 映射文件中的标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
- 注意: 针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
2.2 测试二级缓存
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirLevelCache(){
SqlSession session1 = factory.openSession();
IUSerDao dao1 = session1.getMapper(IUSerDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
session1.close();
SqlSession session2 = factory.openSession();
IUSerDao dao2 = session2.getMapper(IUSerDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
session2.close();
System.out.println(user1==user2);
}
}
运行结果如下:
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存
但是最终两个对象却不是同一个的原因是二级缓存存储的是数据而不是对象。