MyBatis_数据缓存

MyBatis的缓存机制

        在实际项目开发中,通常对数据库查询的性能要求很高,MyBatis提供了查询缓存来缓存数据,从而达到提高查询性能的要求。

MyBatis的查询缓存分为一级缓存二级缓存,一级缓存是SqlSession级别的缓存,二级缓存是Mapper级别的缓存,多个SqlSession共享二级缓存。一级与二级缓存的区别如下图:


1.  一级缓存

       MyBatis的一级缓存是SqlSession级别的缓存,在操作数据库时要构造SqlSession对象,一级缓存的作用域就是同一个SqlSession,在对象中提供有一个HashMap来存储缓存数据。不同的SqlSession之间的缓存数据区域之间是互不影响的。

       在同一个SqlSession中执行两次相同的sql语句,第一次执行完毕会将数据库中查询到的数据写到缓存(内存),第二次查询是会直接从缓存中获取数据,而不再查询数据库,从而提高查询效率。当一个SqlSession结束后(被关闭),该SqlSession中的一级缓存也就不存在了。MyBatis默认开启一级缓存,不用再手动设置。

       注意:MyBatis的缓存机制是基于id进行缓存的,MyBatis使用HashMap存储缓存数据是,使用对象的id作为key,而对象作为value保存。

一级缓存的测试:

1) MyBatis配置文件(SqlMapConfig.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />

	<!-- 定义别名 -->
	<typeAliases>
		<package name="com.sf.po" />
	</typeAliases>


	<!-- 和spring整合后 environments配置将废除 -->
	<environments default="development">
		<environment id="development">
			<!-- 使用jdbc事务管理 -->
			<transactionManager type="JDBC" />
			<!-- 数据库连接池 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<!-- 自动扫描mapper -->
		<package name="com.sf.mapper" />
	</mappers>
</configuration>
2) 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">
<mapper namespace="com.sf.mapper.UserMapper">
	<!-- 查询用户 -->
	 <select id="findUserById" parameterType="int" resultType="user">
	 	SELECT * FROM user WHERE id=#{id}
	 </select>
	 
	 <!-- 更新用户信息 -->
	 <select id="updateUser" parameterType="user">
	 	update user set username=#{username} where id=#{id}
	 </select>
	 
</mapper>

3) UserMapper.java
package com.sf.mapper;

import com.sf.po.User;

public interface UserMapper {
	/**
	 * 通过id获取用户信息
	 */
	public User findUserById(int id) throws Exception;

	/**
	 * 更新用户信息
	 */
	public void updateUser(User user) throws Exception;
}
4) 测试TestCache.java
package com.sf.junit.test;

import java.io.InputStream;

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;
import org.junit.Test;

import com.sf.mapper.UserMapper;
import com.sf.po.User;

/**
 * 测试一级缓存的使用
 * mybatis默认支持一级缓存,不需要在配置文件中去手动配置
 * 一级缓存是sqlSession级别的缓存;
 * 在操作数据库时需要构造sqlSession对象,对象中有一个数据结构(HashMap)用于存储缓存数据
 * 不同的sqlSession之间的缓存数据区域(HashMap)之间是互不影响的
 *
 * @author sf
 * @time 2017-3-22 下午8:04:58
 */
public class TestCache {
	private static SqlSessionFactory sqlSessionFactory;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

	}

	@Test
	public void testFindUserById() throws Exception {
		SqlSession sqlSession = sqlSessionFactory.openSession();

		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

		User user1 = userMapper.findUserById(10);
		System.out.println(user1);

		User user2 = userMapper.findUserById(10);
		System.out.println(user2);

		sqlSession.close();
	}
}
5) 执行结果:

       可以看到,在第一次查询id为10的User时执行了select语句,但是第二次执行查询时就没有再执行select语句,即一级缓存中缓存了id为10的User对象,MyBatis直接将该对象从缓存中取出来,并没有再去查询数据库。

       需要注意的是:sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。

      在上面的TestCache.java中加入如下代码:

	User user1 = userMapper.findUserById(10);
	System.out.println(user1);
	user1.setUsername("王二麻子");

	/*
	 * 如果sqlSession执行commit操作(插入、修改、删除),清空sqlSession的一级缓存,
	 *  目的是为了让缓存中存储的是最新的数据,避免脏读
	 */
	sqlSession.commit();
	User user2 = userMapper.findUserById(10);
	System.out.println(user2);


       会看到执行了两次select操作,即sqlSession执行commit操作时,清空了一级缓存。

2.     二级缓存

       二级缓存是Mapper级别的缓存,是根据mapper的namespace划分的。相同namespace的mapper查询数据存放在同一个区域。在使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,其同样是使用HashMap进行数据缓存的。

       相比一级缓存,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则多义词操作会将数据库中查询到的数据写到缓存(内存),第二次查询是会直接从二级缓存中读取,提高查询效率。

二级缓存的测试:

1) 开启二级缓存:

在SqlMapConfig.xml中加入:

<!-- 二级缓存 需要在SqlMapConfig中设置二级缓存的总开关 -->
	<settings>
		<setting name="cacheEnabled" value="true" />
	</settings>

cacheEnabled的value为true即表示开启二级缓存,默认为false

在UserMapper.xml中加入:

<!-- 二级缓存
	 二级缓存除了要在SqlMapConfig.xml中配置总开关,还要在具体的Mapper.xml中开启二级缓存
	 在UserMapper.xml中开启二级缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)
	  -->
	 <cache/>

表示此mapper开启二级缓存。

cache表示开启当前mapper的namespace下的二级缓存,该元素的属性设置如下:

  •  flushInterval:刷新间隔,可以被设置为任意的正整数(毫秒),默认情况下不设置,仅仅调用语句时刷新缓存数据。
  • size:缓存数目,默认为1024。
  • readOnly:只读。属性可以被设置为true或false。只读的属性会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。可读写的缓存会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,故默认是false。

2)实现序列化

       使用二级缓存时,与查询结果映射的Java对象必须实现java.io.Serializable接口的序列化和反序列化操作,如果存在父类,其成员都需要实现序列化接口。实现序列化接口是为了对缓存据进行序列化和反序列化操作,因为额级缓存数据的存储介质多种多样,不一定在内存,可能在硬盘或服务器。

public class User implements Serializable {
	/*
	 * 要使用二级缓存,还需要让pojo类实现Serializable接口
	 * 为了将二级缓存数据取出,执行反序列化操作,因为二级缓存的存储介质多种多样,不一定在内存
	 */
	private int id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;

3) 测试:

@Test
	public void testFindUserById() throws Exception {
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();

		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
		User user1 = userMapper1.findUserById(10);
		System.out.println(user1);
		//这里执行sqlSession的关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();

		
		UserMapper userMapper2 = sqlSession1.getMapper(UserMapper.class);
		User user2 = userMapper2.findUserById(10);
		System.out.println(user2);
		sqlSession2.close();

	}


可以看到只执行了一次select操作

4) 禁用缓存

       在UserMapper.xml中的select语句中设置useCache="false"可以禁用当前select语句的二级缓存,该属性值的默认值为true。

<select id="findUserById"parameterType="int" resultType="user" useCache="false">
         SELECT* FROM user WHERE id=#{id}
</select>

结果:


可以看到执行了两次select操作。

5) 刷新缓存:

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

@Test
	public void testFindUserById() throws Exception {
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();

		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
		User user1 = userMapper1.findUserById(10);
		System.out.println(user1);
		//这里执行sqlSession的关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();

		/*
		 * 测试执行舒心缓存操作时,清空缓存
		 * 结果会执行两次数据库查询
		 */
		UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
		User user3 = userMapper3.findUserById(1);
		user3.setUsername("点点滴滴");
		userMapper3.updateUser(user3);
		sqlSession3.commit();
		sqlSession3.close();

		UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
		User user2 = userMapper2.findUserById(10);
		System.out.println(user2);
		sqlSession2.close();

	}

<!-- 更新用户信息 -->
	 <select id="updateUser" parameterType="user" flushCache="true">
	 	update user set username=#{username} where id=#{id}
	 </select>
结果:

可以看到执行了两侧select操作,说明执行update操作时刷新了二级缓存中的数据。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值