缓存:
- 缓存是将用户经常需要查询的数据放入到内存(缓存)中,用户不需要经常访问磁盘,提高查询效率,解决并发性能问题;
- MyBatis 持久层缓存
MyBatis 提供一级缓存和二级缓存:
① MyBatis 一级缓存是一个 sqlSession级别的,sqlSession对象只能访问自己的一级缓存;
② MyBatis 二级缓存是 mapper级别的;对于 mapper级别的缓存,不同的 sqlSession是可以共享的;
1. 一级缓存:
原理:
① 第一次发出查询,写入 sqlSession缓存区,同一个 sqlSession再次发出相同的 sql,从缓存中取数据;
② 如果两次操作中间有 commit操作,一级缓存区全部清空,下次再要查询,先操作数据库,再写入缓存;
目的:防止查询出脏数据;(eg:执行 update操作,数据库中数据已改,但缓存区中未改,如果清空缓存区,直接再次从缓存区读取数据,会查询出脏数据)
③ 一级缓存在 MyBatis中默认支持,无需配置;
④ 如果缓存中存在数据,则直接返回;如果缓存中不存在数据,查数据库
UserMapper.java
public interface UserMapper {
public User findUserById(int id) throws Exception;
public void updateUser(User user) throws Exception;
}
AppTest1.java
public class AppTest {
SqlSessionFactory sqlSessionFactory=null;
@Before
public void init() throws IOException {
InputStream inputStream=Resources.getResourcesAsStream("SqlMapConfig.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test() throws Exception(){
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
//第一次
User user=userMapper.findUserById(1);
System.out.println(user.getId()+","+user.getUsername()+","+user.getAge());
//第二次
User user2=userMapper.findUserById(1);
System.out.println(user2.getId()+","+user2.getUsername()+","+user2.getAge());
sqlSession.close();
}
}
运行结果:
(两次都查询 id=1的用户,sql 语句只执行一次,第二次是从缓存中获取)
AppTest2.java
public class AppTest {
SqlSessionFactory sqlSessionFactory=null;
@Before
public void init() throws IOException {
InputStream inputStream=Resources.getResourcesAsStream("SqlMapConfig.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test() throws Exception(){
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
//第一次
User user=userMapper.findUserById(1);
System.out.println(user.getId()+","+user.getUsername()+","+user.getAge());
//第二次
User user2=userMapper.findUserById(2);
System.out.println(user2.getId()+","+user2.getUsername()+","+user2.getAge());
sqlSession.close();
}
}
运行结果:
(两次查询 id不同的用户,sql 语句肯定执行两次)
AppTest3.java
public class AppTest {
SqlSessionFactory sqlSessionFactory=null;
@Before
public void init() throws IOException {
InputStream inputStream=Resources.getResourcesAsStream("SqlMapConfig.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test() throws Exception(){
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
//第一次
User user=userMapper.findUserById(1);
System.out.println(user.getId()+","+user.getUsername()+","+user.getAge());
//添加一个commit操作
user.setUsername("jay");
userMapper.updateUser(user);
sqlSession.commit();
//第二次
User user2=userMapper.findUserById(1);
System.out.println(user2.getId()+","+user2.getUsername()+","+user2.getAge());
sqlSession.close();
}
}
运行结果:
(两次都查询 id=1的用户,中间执行了 update操作,清空了缓存区,sql 语句执行两次)
1.二级缓存:
二级缓存场景:
对于查询频率高,变化频率低,使用二级缓存;
二级缓存降低对数据库的访问,提高访问速度。
局限性:
对于数据量较多的查询缓存,不方便。
原理:
1. 二级缓存的范围: Mapper 级别(同一个命名空间),Mapper以命名空间为单位,创建缓存区域;
2. 二级缓存的配置:
1. 全局配置文件中 开启全局缓存;
<setting name="cacheEnabled" value="true"/>
2. mapper映射文件中添加一行,开启 mapper映射文件的二级缓存;
<cache></cache>
3. 查询结果映射到 POJO, POJO类实现Serializable接口;(二级缓存可以将内存的数据写入磁盘,对象进行序列化和反序列化)
3. 二级缓存(开启后对某些 statement)的禁用:
statement 设置禁用 useCache=“false”(默认为 true)
<mapper namespace="com.mdd.mapper.UserMapper">
<cache></cache>
<select id="findUserById" parameterType="int" resultType="user" useCache="false">
select * from users where id=#{id}
</select>
</mapper>
4. 刷新缓存(二级缓存的刷新):
如果 sqlSession有 commit操作,会默认对二级缓存进行刷新 (默认为 true);若对对应的 statement设置 flushCache=“false” 则关闭了二级缓存的刷新。
<mapper namespace="com.mdd.mapper.UserMapper">
<cache></cache>
<!-- 关闭了二级缓存的刷新(修改用户id一定要有值) -->
<update id="updateUser" parameterType="com.mdd.po.User" flashCache="false">
update users set username=#{username},age=#{age} where id=#{id}
</update>
<select id="findUserById" parameterType="int" resultType="user" useCache="false">
select * from users where id=#{id}
</select>
</mapper>
SqlMapConfig.xml
<configuration>
<settings> //开启全局缓存
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliase> //别名定义
<package name="com.iotek.po"/>
</typeAliase>
<properties resource="db.prperties"></properties> //加载jdbc连接属性文件
<environments default="mysql"> //环境变量配置
<environment id="mysql">
<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.iotek.mapper"/>
</mappers>
</configuration>
UserMapper.xml
<mapper namespace="com.mdd.mapper.UserMapper">
<cache></cache> //开启mapper映射文件的二级缓存
//关闭mapper映射文件中某些statement的二级缓存(在对应statement后加useCache="false")
<select id="findUserById" parameterType="int" resultType="user" useCache="false">
select * from users where id=#{id}
</select>
</mapper>
User.java
public class User implements Serializable{//实现Serializable接口
private int id;
private String username;
private int age;
set、get();
}
AppTest.java
public class AppTest {
SqlSessionFactory sqlSessionFactory=null;
@Before
public void init() throws IOException {
InputStream inputStream=Resources.getResourcesAsStream("SqlMapConfig.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserList() throws Exception(){
SqlSession sqlSession1=sqlSessionFactory.openSession();
SqlSession sqlSession2=sqlSessionFactory.openSession();
UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class);
//第一次
User user1=userMapper1.findUserById(1);
System.out.println(user1.getId()+","+user1.getUsername());
sqlSession1.close();
//第二次
User user2=userMapper2.findUserById(1);
System.out.println(user2.getId()+","+user2.getUsername());
sqlSession2.close();
}
}
运行结果:
(第一次查找缓存命中率为0,所以从数据库中查找;第二次直接在缓存中查找)
运行结果(禁用 id="findUserById"的 statement后):
(禁用后没有二级缓存,每次查询都要从数据库中查询,所以执行两次 SQL语句)
开启或关闭了二级缓存的刷新后:
AppTest.java
public class AppTest {
SqlSessionFactory sqlSessionFactory=null;
@Before
public void init() throws IOException {
InputStream inputStream=Resources.getResourcesAsStream("SqlMapConfig.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserList() throws Exception(){
SqlSession sqlSession1=sqlSessionFactory.openSession();
SqlSession sqlSession2=sqlSessionFactory.openSession();
UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class);
//第一次
User user1=userMapper1.findUserById(1);
System.out.println(user1.getId()+","+user1.getUsername());
sqlSession1.close();
//commit操作
SqlSession sqlSession3=sqlSessionFactory.openSession();
UserMapper userMapper3=sqlSession3.getMapper(UserMapper.class);
user1.setUsername("Steve");
userMapper3.updateUser(user);
sqlSession1.commit();
sqlSession1.close();
//第二次
User user2=userMapper2.findUserById(1);
System.out.println(user2.getId()+","+user2.getUsername());
sqlSession2.close();
}
}
开启了二级缓存的刷新即是默认状态或 mapper映射文件中对应的 statement添加 flushCache=“true”:
(更新进行 commit操作,刷新了缓存;第二次查找,缓存命中率为0,从数据库中查找)
运行结果:
关闭了二级缓存的刷新即不是默认状态,mapper映射文件中对应的 statement添加 flushCache=“false”:
(更新进行 commit操作,没有刷新缓存;第二次查找,缓存命中率为0.5,查找的是未更新之前的数据)
运行结果: