mybatis的一级缓存和二级缓存


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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值