MyBatis缓存
先来回答几个问题:
- 什么是缓存?
存在内存中的临时数据 - 为什么使用缓存
减少和数据库的交互次数,提高执行效率 - 什么样的数据能使用缓存,什么样的数据不用缓存
经常查询且不经常修改的数据适合使用缓存,经常修改的数据不适合使用缓存;由于缓存的使用会产生数据不一致的问题,所以数据的正确性对最终结果影响很大的也不适合使用缓存
MyBatis中的一级缓存和二级缓存
一级缓存
它是指MyBatis中的SqlSession对象的缓存
当我们执行查询之后,查询结果会同时存入到SqlSession为我们提供的一块区域中,该区域是一个map结构,当我们再次查询同样的数据,MyBatis先去SqlSession中查询是否已经存在,如果存在直接拿来使用.
当SQLSession对象消失,MyBatis的一级缓存也就消失了.
为了证明上面说的,我们给出代码例子:
User类
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setAddress(String address) {
this.address = address;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public String getUsername() {
return username;
}
public String getAddress() {
return address;
}
public String getSex() {
return sex;
}
public Date getBirthday() {
return birthday;
}
}
这里并没有重写toString()方法,主要是为了一会方便观察现象.
dao中方法:
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
xml文件内容:
<!--根据id查询用户信息-->
<select id="findById" parameterType="Integer" resultType="org.zjb.domain.User">
select * from user where id=#{id};
</select>
测试类:
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache() throws IOException {
init();
User user1 = userDao.findById(41);
System.out.println(user1);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println("user1是否等于user2? " + (user1 == user2));
destroy();
}
测试类中,我们分别使用两个对象来接受两次查询的结果,最后比较两个对象的地址.
日志结果如下:
从这个结果可以看出,我们调用了两次方法,但是sql语句只执行了一次,而且两个对象的地址一样,这说明两个对象是同一个,缓存起作用了!
好,现在我们修改一下测试类,将SQLSession对象重新创建,来证明一级缓存的消息
test测试类修改为如下:
/**
* 测试清理一级缓存
*/
@Test
public void testFirstLevelCache() throws IOException {
init();
User user1 = userDao.findById(41);
System.out.println(user1);
// 关闭缓存,测试一级缓存清理的情况
sqlSession.close();
// 再次获取SqlSession对象
sqlSession = factory.openSession();
// 重新获取代理对象
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println("user1是否等于user2? " + (user1 == user2));
destroy();
}
执行结果如下:
可以看见,sqlSession对象重新创建之后,缓存确实消失了,sql语句执行了两次.
除了上面的主动close之外,SqlSession提供了专门的方法清空缓存,为sqlSession.clearCache();
下面给出演示代码:
test测试代码
/**
* 测试调用方法清空缓存
*/
@Test
public void testFirstLevelCache() throws IOException {
init();
User user1 = userDao.findById(41);
System.out.println(user1);
// 主动调用方法清空缓存对象
sqlSession.clearCache();
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println("user1是否等于user2? " + (user1 == user2));
destroy();
}
这个方法的执行结果如下:
可以看见和上面的主动释放资源是一致的.
一级缓存大概搞明白了,现在来了一个新的问题:
如果数据库的数据跟我们的一级缓存不一致了,MyBatis是如何做到数据同步的呢?
我们现在直接看例子:
test测试方法:
/**
* 测试刷新缓存
* @throws IOException
*/
@Test
public void testClearCache() throws IOException {
init();
// 根据id查询用户
User user1 = userDao.findById(41);
System.out.println(user1);
// 更新用户新
user1.setUsername("娃哈哈");
user1.setAddress("陕西省");
userDao.updateUser(user1);
// 再次查询id为41的用户
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println("user1和user2是否相等? " + (user1 == user2));
destroy();
}
运行结果为:
可以看见user1和user2并不相等,我们在更新了数据库信息之后,MyBatis似乎是有某种机制帮助我们更新是缓存.
实际上,当调用sqlSession的修改,添加,删除,commit(), close()等方法时,MyBatis为我们主动清空一级缓存.
二级缓存
它是指MyBatis中SqlSessionFactory对象的缓存,同一个SqlSessionFactory对象创建的SqlSession共享其缓存.
二级缓存的使用步骤如下:
- 让MyBatis框架支持二级缓存,需要在主配置文件中配置(在SqlMapConfig.xml中配置)
<!--配置二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 让当前的映射文件支持二级缓存(在对应dao的xml文件中配置)
mapper标签中加入下面的标签
<!--开启user支持二级缓存-->
<cache/>
- 让当前的操作支持二级缓存(在Select标签中配置)
<!--根据id查询用户信息-->
<!--标签开启二级缓存-->
<select id="findById" useCache="true" parameterType="Integer" resultType="org.zjb.domain.User">
select * from user where id=#{id};
</select>
在完成了上面的操作之后,我们来运行下面的测试代码:
test测试方法
/**
* 测试二级缓存
*/
@Test
public void testSecondLevelCache() throws IOException {
init();
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();// 一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close(); // 一级缓存消失
System.out.println("user1是否等于user2? " + (user1 == user2));
destroy();
}
上面的factory对象是同一个,所以即使创建出多个sqlSession对象,只要他们查询相同的东西都会走缓存,这里user1和user2的地址应该是相同的.
我们看看结果:
可以看见日志信息中,确实只执行了一次sql语句,但是为什么两个对象==的结果为false呢?
实际上MyBatis的二级缓存中存放的并不是一个对象,而是类似于文本的字符串,如果再次需要才会通过反序列化的手段转换为对象,user1和user2内容是一样的,而且确实是一次sql执行的结果,只不过反序列化的过程又构造了新的对象,导致这两个对象实际上内存地址不同,所以是false.由于涉及到序列化和反序列化,需要实现Serializable接口.