mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点和相同点总结如下
不同点:
相同点:
一级缓存
一级缓存是mybatis自带的缓存,mybatis每次在查询后,会将语句和参数相同的查询SQL的结果集存放进缓存,待下一次有相同的语句和参数时,mybatis直接将缓存内的结果集返回,而不再查询数据库。如果对于缓存的数据对应的表有增删改操作的话,缓存自动清空。
通过实际的代码测试来看,在上一次一个简单的mybatis入门demo的基础上改造工程
增加BaseMaperTest.java类,用以进行SqlSession的获取
package cn.mybatis.xml;
import java.io.IOException;
import java.io.Reader;
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.BeforeClass;
/**
* 设置mapper测试父类
* 用以进行数据库连接,获取SqlSession
* @author PC
*/
public class BaseMapperTest {
private static SqlSessionFactory sqlSessionFactory;
/**
* 进行数据库连接
*/
@BeforeClass
public static void init() {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取SqlSession
* @return
*/
public SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
在CountryMapper.java中增加根据id查询方法,增加一个addCountry方法
/**
* 查询国家/地区
* @param id 查询id
* @return 查询到的国家/地区
*/
Country selectCountryById(Long id);
/**
* 添加国家/地区
* @param country
* @return 影响的数据条数
*/
int addCountry(Country country);
对应CountryMapper.xml配置文件
insert into country(id, countryname, countrycode)
values(#{id}, #{countryname}, #{countrycode})
通过Junit来测试,三种场景,分别来测试
/**
* 一级缓存测试
* 测试缓存后,再查询
*/
@Test
public void testCache1() {
SqlSession sqlSession = getSqlSession();
try {
// 第一次查询
Country country = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country.getCountryname() + ":" + country.getCountrycode());
// 通过日志可以发现,第二次查询并未到数据库查数据,说明第二次走的是缓存
Country country2 = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
} finally {
sqlSession.close();
}
}
执行后,可以看到日志
Opening JDBC Connection
Created connection 1291113768.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id, countryname, countrycode from country where id = ?
==> Parameters: 2(Long)
<== Columns: id, countryname, countrycode
<== Row: 2, 美国, US
<== Total: 1
美国:US
美国:US
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
反馈了两次查询结果,但是只查询了一次数据库,说明第二次的查询是取得缓存结果
/**
* 一级缓存测试
* 测试缓存后,增删改查,再查询
*/
@Test
public void testCache2() {
SqlSession sqlSession = getSqlSession();
try {
// 第一次查询
Country country = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country.getCountryname() + ":" + country.getCountrycode());
Country country2 = new Country();
country2.setId(7);
country2.setCountrycode("TW");
country2.setCountryname("中国台湾");
int result = sqlSession.insert("addCountry", country2);
if (result == 1) {
System.out.println("** insert success **");
}
// 由于进行了insert操作,第二次查询没有走缓存,直接走的数据库查询
Country country3 = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country3.getCountryname() + ":" + country3.getCountrycode());
} finally {
sqlSession.commit();
sqlSession.close();
}
}
执行结果
Opening JDBC Connection
Created connection 1291113768.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id, countryname, countrycode from country where id = ?
==> Parameters: 2(Long)
<== Columns: id, countryname, countrycode
<== Row: 2, 美国, US
<== Total: 1
美国:US
==> Preparing: insert into country(id, countryname, countrycode) values(?, ?, ?)
==> Parameters: 7(Integer), 中国台湾(String), TW(String)
<== Updates: 1
** insert success **
==> Preparing: select id, countryname, countrycode from country where id = ?
==> Parameters: 2(Long)
<== Columns: id, countryname, countrycode
<== Row: 2, 美国, US
<== Total: 1
美国:US
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
通过日志,可以印证,在缓存后,如果执行增删改操作,之前缓存的数据会自动清空。
此时,我们需要在查询语句的标签中增加 flushCache="true" ,意为查询结果不缓存。
增加后的SQL标签为
测试代码
/**
* 一级缓存测试
* 测试select查询,不存入缓存,再查询
*/
@Test
public void testCache3() {
SqlSession sqlSession = getSqlSession();
try {
// 第一次查询,但是SQL设置了flushCache="true",即查询结果不会缓存
Country country = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country.getCountryname() + ":" + country.getCountrycode());
// 通过日志可以发现,第二次查询依然查询了数据库,查询出来的结果依然不会缓存
Country country2 = sqlSession.selectOne("selectCountryById", 2l);
System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
} finally {
sqlSession.close();
}
}
执行后的日志
Opening JDBC Connection
Created connection 1291113768.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id, countryname, countrycode from country where id = ?
==> Parameters: 2(Long)
<== Columns: id, countryname, countrycode
<== Row: 2, 美国, US
<== Total: 1
美国:US
==> Preparing: select id, countryname, countrycode from country where id = ?
==> Parameters: 2(Long)
<== Columns: id, countryname, countrycode
<== Row: 2, 美国, US
<== Total: 1
美国:US
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
可以看出,两次查询都走了数据库查询,原因就在于flushCache="true" 上面。flushCache标签,意为查询结果后是否缓存,默认为false,即默认存入缓存,方便后续查询。如果不想存缓存,则需要手动的设置为true,查询结果不会存缓存。一般不推荐这么做,这么做会增加数据库的负担,增加一些不必要的查询。
通过上面的测试,一级缓存的场景可以总结如下:
直接查,存缓存;增删改,清缓存;flushCache不缓存。
这里的flushCache值得是手动设置flushCache为true的情形。
二级缓存
相较于一级缓存的自动默认开启,二级缓存需要手动开启。一级缓存在同一个SqlSession内,以SqlSession为缓存单位;二级缓存在不同的SqlSession间,以mapper为单位,即不同的SqlSession间可以共享相同的mapper下接口查询的数据。
准备测试环境
增加SysUser.java
package cn.mybatis.xml.model;
import java.io.Serializable;
import java.util.Date;
/**
* 用户表
* @author PC
*/
public class SysUser implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String userPassword;
/**
* 邮箱
*/
private String userEmail;
/**
* 简介
*/
private String userInfo;
/**
* 头像
*/
private byte[] headImg;
/**
* 创建时间
*/
private Date createTime;
getter and setter...
}
mybatis-config.xml中增加UserMapper.xml配置
UserMapper.xml内容
insert into sys_user(id, user_name,user_password,user_email,user_info) values(#{id}, #{userName}, #{userPassword}, #{userEmail}, #{userInfo});
delete from sys_user where id = #{id}
上面,看到了
通过Junit来测试,主要测试三种不同的场景
/**
* 二级缓存测试
* 不同SqlSession之间的查询
*/
@Test
public void testCache1() {
SqlSession sqlSession = getSqlSession();
try {
SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
System.out.println(user.getUserName());
} finally {
sqlSession.close();
}
// 二级缓存,在第一个session关闭时,数据存入二级缓存中
SqlSession sqlSession2 = getSqlSession();
try {
SysUser user = sqlSession2.selectOne("selectUserByUserId", 1001l);
System.out.println(user.getUserName());
} finally {
sqlSession.close();
}
}
执行后,通过日志可看到
Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
Opening JDBC Connection
Created connection 1291113768.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ?
==> Parameters: 1001(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1001, test, 123456, [email protected], <>, <>, 2017-04-01 12:00:01.0
<== Total: 1
test
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.5
test
执行了一次数据库查询,第二次查询时直接通过获取缓存的值返回,证明二级缓存生效。
/**
* 二级缓存测试
* 不同SqlSession之间查询,增删改,再查询
*/
@Test
public void testCache2() {
SqlSession sqlSession = getSqlSession();
try {
SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
System.out.println(user.getUserName());
} finally {
sqlSession.close();
}
SqlSession sqlSession2 = getSqlSession();
try {
int result = sqlSession2.delete("deleteUser", 2001l);
System.out.println(result);
} finally {
sqlSession2.commit();
sqlSession2.close();
}
// 第二次查询
SqlSession sqlSession3 = getSqlSession();
try {
SysUser user = sqlSession3.selectOne("selectUserByUserId", 1001l);
System.out.println(user.getUserName());
} finally {
sqlSession.close();
}
}
执行后,看日志
Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
Opening JDBC Connection
Created connection 1291113768.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ?
==> Parameters: 1001(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1001, test, 123456, [email protected], <>, <>, 2017-04-01 12:00:01.0
<== Total: 1
test
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
Opening JDBC Connection
Checked out connection 1291113768 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: delete from sys_user where id = ?
==> Parameters: 2001(Long)
<== Updates: 1
1
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
Returned connection 1291113768 to pool.
Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
Opening JDBC Connection
Checked out connection 1291113768 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
==> Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ?
==> Parameters: 1001(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1001, test, 123456, [email protected], <>, <>, 2017-04-01 12:00:01.0
<== Total: 1
test
可以发现,在执行了delete语句后,缓存被清空了,待第二次查询时,又查了数据库。
其实上面在说一级缓存时有说到,任何的增删改的语句,都会清空一级缓存,二级缓存自然会被清空了。
上面的两个场景测试,都是在mapper文件中设置使用二级缓存,二级缓存其实还有一个总开关,在mybatis-config.xml文件的setting配置中,为何之前并没有去配这个开关呢,这个开关默认打开的,只需要在mapper.xml文件中配置二级缓存开关即可。
该标签为cacheEnabled,默认值为true,所以,默认的二级缓存的总开关是打开的,不需要手动设置。
我们现在将其设置为false,再次执行二级缓存场景一的测试语句
可以看到日志
Opening JDBC Connection
Created connection 345281752.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
==> Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ?
==> Parameters: 1001(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1001, test, 123456, [email protected], <>, <>, 2017-04-01 12:00:01.0
<== Total: 1
test
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
Returned connection 345281752 to pool.
Opening JDBC Connection
Checked out connection 345281752 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
==> Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ?
==> Parameters: 1001(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1001, test, 123456, [email protected], <>, <>, 2017-04-01 12:00:01.0
<== Total: 1
test
两次查询,均走了数据库查询。
综上:二级缓存可以作为一级缓存的补充,一级缓存在同一个SqlSession之间,二级缓存将缓存扩大到不同的SqlSession之间。相同的点是,一旦有增删改的操作,缓存均会清空。
以上是本人学习mybatis缓存的简单认知,记录下来,供初学的童鞋予以参考。如果内容有错误或疏漏部分,还望批评指正。谢谢。
附 工程源代码路径:mybatis一级缓存和二级缓存简单示例