Mybatis一级缓存与二级缓存

mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不一样点和相同点总结以下html

不一样点:java

  • 一级缓存存在于一个SqlSession以内,二级缓存存在于不一样的SqlSession之间
  • 一级缓存不须要手动开启,属于默认开启状态;二级缓存须要手动开启

相同点:mysql

  • 在增删改SQL以后,缓存会自动清空
  • flushCache="true"的查询语句查询内容不存放进缓存

 

一级缓存sql

一级缓存是mybatis自带的缓存,mybatis每次在查询后,会将语句和参数相同的查询SQL的结果集存放进缓存,待下一次有相同的语句和参数时,mybatis直接将缓存内的结果集返回,而再也不查询数据库。若是对于缓存的数据对应的表有增删改操做的话,缓存自动清空。数据库

经过实际的代码测试来看,在上一次一个简单的mybatis入门demo的基础上改造工程apache

增长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方法session

/**
	 * 查询国家/地区
	 * @param id 查询id
	 * @return 查询到的国家/地区
	 */
	Country selectCountryById(Long id);
	
	/**
	 * 添加国家/地区
	 * @param country
	 * @return 影响的数据条数
	 */
	int addCountry(Country country);

对应CountryMapper.xml配置文件mybatis

<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来测试,三种场景,分别来测试app

  • 缓存后,直接查询
/**
	 * 一级缓存测试
	 * 测试缓存后,再查询
	 */
	@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>

上面,看到了<cache/>,这个标签标示启用二级缓存,二级缓存有一系列的参数策略,这里不配置任何内容,表示均使用默认值。

经过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

执行了一次数据库查询,第二次查询时直接经过获取缓存的值返回,证实二级缓存生效。

  • 缓存后,执行增删改,再查询
/**
	 * 二级缓存测试
	 * 不一样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

两次查询,均走了数据库查询。

 

综上:二级缓存能够做为一级缓存的补充,一级缓存在同一个SqlSession之间,二级缓存将缓存扩大到不一样的SqlSession之间。相同的点是,一旦有增删改的操做,缓存均会清空。

 

以上是本人学习mybatis缓存的简单认知,记录下来,供初学的童鞋予以参考。若是内容有错误或疏漏部分,还望批评指正。谢谢。

 

附 工程源代码路径:mybatis一级缓存和二级缓存简单示例