mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点和相同点总结如下
不同点:
- 一级缓存存在于一个SqlSession之内,二级缓存存在于不同的SqlSession之间
- 一级缓存不需要手动开启,属于默认开启状态;二级缓存需要手动开启
相同点:
在增删改SQL之后,缓存会自动清空
flushCache="true"的查询语句查询内容不存放进缓存
一级缓存
一级缓存是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配置文件
<select id="selectCountryById" resultType="cn.mybatis.xml.model.Country">
select id, countryname, countrycode
from country
where id = #{id}
</select>
<insert id="addCountry">
insert into country(id, countryname, countrycode)
values(#{id}, #{countryname}, #{countrycode})
</insert>
通过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 id="selectCountryById" flushCache="true" resultType="cn.mybatis.xml.model.Country">
select id, countryname, countrycode
from country
where id = #{id}
</select>
测试代码
/**
* 一级缓存测试
* 测试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配置
<mappers>
<mapper resource="cn/mybatis/xml/mapper/CountryMapper.xml" />
<mapper resource="cn/mybatis/xml/mapper/UserMapper.xml" />
</mappers>
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">
<!-- 定义当前XML的命名空间 -->
<mapper namespace="cn.mybatis.xml.mapper.UserMapper">
<!-- 启用mybatis二级缓存,不设置具体数值,均为默认项 -->
<cache/>
<!-- resultMap 设置返回值的类型和映射关系 -->
<resultMap id="userMap" type="cn.mybatis.xml.model.SysUser">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="userInfo" column="user_info"/>
<result property="headImg" column="head_img" jdbcType="BLOB"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- ID属性,定义当前select查询的唯一ID;resultType,定义当前查询的返回值类型,上面设置了resultMap的别名,这里引用id即可 -->
<select id="selectUserByUserId" resultMap="userMap">
select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = #{id}
</select>
<!-- 增加user -->
<insert id="addUser">
insert into sys_user(id, user_name,user_password,user_email,user_info) values(#{id}, #{userName}, #{userPassword}, #{userEmail}, #{userInfo});
</insert>
<delete id="deleteUser">
delete from sys_user where id = #{id}
</delete>
</mapper>
上面,看到了,这个标签标示启用二级缓存,二级缓存有一系列的参数策略,这里不配置任何内容,表示均使用默认值。
通过Junit来测试,主要测试三种不同的场景
不同SqlSession之间的查询
/**
* 二级缓存测试
* 不同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, test@1.com, <<BLOB>>, <<BLOB>>, 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
Cache Hit Ratio 表示缓存命中率。
开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。
执行了一次数据库查询,第二次查询时直接通过获取缓存的值返回,证明二级缓存生效。
缓存后,执行增删改,再查询
/**
* 二级缓存测试
* 不同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, test@1.com, <<BLOB>>, <<BLOB>>, 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, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
<== Total: 1
test
可以发现,在执行了delete语句后,缓存被清空了,待第二次查询时,又查了数据库。
其实上面在说一级缓存时有说到,任何的增删改的语句,都会清空一级缓存,二级缓存自然会被清空了。
关闭二级缓存总开关
上面的两个场景测试,都是在mapper文件中设置使用二级缓存,二级缓存其实还有一个总开关,在mybatis-config.xml文件的setting配置中,为何之前并没有去配这个开关呢,这个开关默认打开的,只需要在mapper.xml文件中配置二级缓存开关即可。
<settings>
<!-- mybatis 二级缓存总开关,总开关默认为true -->
<!-- 总开关打开后,然后在每个mapper中设置自己的二级缓存开关;若总开关关闭,则后续mapper设置均无效 -->
<setting name="cacheEnabled" value="false"/>
<!-- 设置驼峰匹配 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!-- 配置指定使用log4j输出日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
该标签为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, test@1.com, <<BLOB>>, <<BLOB>>, 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, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
<== Total: 1
test
两次查询,均走了数据库查询。
二级缓存的使用原则
- 1.只能在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。 - 2.在单表上使用二级缓存
如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。 - 3.查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
综上:
增删改操作,无论是否进行提交sqlSession.commit(),均会清空一级、二级缓存,使查询再次从DB中select。
二级缓存可以作为一级缓存的补充,一级缓存在同一个SqlSession之间,二级缓存将缓存扩大到不同的SqlSession之间。相同的点是,一旦有增删改的操作,缓存均会清空。
以上是本人学习mybatis缓存的简单认知,记录下来,供初学的童鞋予以参考。如果内容有错误或疏漏部分,还望批评指正。谢谢。
转载链接:https://blog.csdn.net/magi1201/article/details/85524712