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配置文件

	
	
	
		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之间的查询
	/**
	 * 二级缓存测试
	 * 不同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一级缓存和二级缓存简单示例

你可能感兴趣的:(mybatis)