MyBatis从零开始-MyBatis缓存配置

系列博客目录:MyBatis从零开始博客目录

6. MyBatis缓存配置

​ 使用缓存可以使应用更快地获取数据,避免频繁的数据库交互,尤其是在查询越多、缓存命中率越高的情况下,使用缓存的作用就越明显。MyBatis作为持久化框架,提供了非常强大的查询缓存特性,可以非常方便地配置和定制使用。

​ MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。

​ 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存);

​ 二级缓存需要手动开启和配置,他是基于namespace级别的缓存;

​ 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

6.1 一级缓存

一级缓存也叫本地缓存:SqlSession

​ 与数据库同义词回话期间查询得到的数据会放在本地缓存中。

​ 以后如果需要获取相同的数据,直接从缓存中拿,没有必要再去查询数据库。

测试代码如下:

CacheTest.java

package com.xiangty.test;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import com.xiangty.bean.User;
import com.xiangty.mapper.UserMapper;

public class CacheTest {
    
    

	@Test
	public void testL1Cache() {
    
    
		SqlSession sqlSession = SqlSessionUtil.getSqlSession();
		try {
    
    
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			User user = userMapper.getUserById(1);
			System.out.println(user);
			
			System.out.println("=============================================");
			User user2 = userMapper.getUserById(1);
			System.out.println(user2);
			
			System.out.println(user == user2);
			
		} finally {
    
    
			sqlSession.close();
		}
	}

}

输出结果:
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
User [id=1, username=管理员, password=123456]
true

​ 如上代码,发现在同一个SqlSession回话中,getUserById方法查询相同id的信息,查询出来的对象是相等的,而且日志中可以看到查询的SQL语句只有一个,说明两次查询存在缓存,第二次查询用到了第一次查询的结果。

缓存失效的情况:

1.查询不同的东西;

2.增删改操作,可以能会改变原来的数据,所以必定会刷新缓存;

3.查询不同的Mapper;

4.手动清理缓存。

Cache.java部分代码实例如下:

/**
 * 查询不同的东西
 */
@Test
public void testL2Cache() {
    
    
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // id=1
        User user = userMapper.getUserById(1);
        System.out.println(user);

        System.out.println("=============================================");
        // id=2
        User user2 = userMapper.getUserById(2);
        System.out.println(user2);

        System.out.println(user == user2);

    } finally {
    
    
        sqlSession.close();
    }
}

运行结果:
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 2, 路人甲, 123456
DEBUG [main] - <==      Total: 1
User [id=2, username=路人甲, password=123456]
false
/**
 * 增删改操作,可以能会改变原来的数据,所以必定会刷新缓存;
 */
@Test
public void testL3Cache() {
    
    
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        System.out.println(user);

        User deleteUser = new User();
        deleteUser.setUsername("password1");
        userMapper.deleteByUsername(deleteUser);
        sqlSession.commit();

        System.out.println("=============================================");
        User user2 = userMapper.getUserById(1);
        System.out.println(user2);

        System.out.println(user == user2);

    } finally {
    
    
        sqlSession.close();
    }
}

运行结果:
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
DEBUG [main] - ==>  Preparing: DELETE FROM user WHERE username LIKE CONCAT('%',?,'%') 
DEBUG [main] - ==> Parameters: password1(String)
DEBUG [main] - <==    Updates: 5
=============================================
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
false
/**
 * 手动清除缓存
 */
@Test
public void testL4Cache() {
    
    
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        System.out.println(user);

        sqlSession.clearCache(); // 清除缓存

        System.out.println("=============================================");
        User user2 = userMapper.getUserById(1);
        System.out.println(user2);

        System.out.println(user == user2);

    } finally {
    
    
        sqlSession.close();
    }
}

运行结果:
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
false

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这段时间区间。

6.2 二级缓存

6.2.1 二级缓存配置

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
基于namespace级别的缓存,一个名称空间,对应一个二级缓存(但是前提是操作必须在同一个SqlSessionFactory 中进行)。
工作机制:
​ 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
​ 如果当前会话关闭了,这个会话对应的一级缓存就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
​ 新的会话查询信息,就可以从二级缓存中获取内容;
​ 不同的mapper查出的数据会放在自己对应的缓存中。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

在这里插入图片描述

可以通过 cache 元素的属性来修改。比如:

<cache eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

测试代码如下
mybatis-config.xml 新增setting配置

<?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>
	<settings>
		<!-- 显示开启全局缓存 -->
		<setting name="cacheEnabled" value="true"></setting>
	</settings>
</configuration>

UserMapper.xml新增

<cache/>

在这里插入图片描述
也可以自定一些参数如下:

	<cache eviction="FIFO"
		  flushInterval="60000"
		  size="512"
		  readOnly="true"/>

在这里插入图片描述

User.java 序列化

import java.io.Serializable;
public class User implements Serializable {
    
    
	private static final long serialVersionUID = -6730295119137974064L;
    // 省略属性、get和set方法等方法
}

SqlSessionUtil.java 新增getSqlSessionFactory方法

public static SqlSessionFactory getSqlSessionFactory() {
    
    
		// 此处可以不使用try抛异常,SqlSessionFactoryBuilder().build()方法中有reader流关闭的操作
		try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml");) {
    
    
			return new SqlSessionFactoryBuilder().build(reader);
		} catch (IOException e) {
    
    
			e.printStackTrace();
		}
		return null;
	}

CacheTest.java测试代码如下:

/**
 * 测试二级缓存
 */
@Test
public void testL6Cache() {
    
    
    SqlSessionFactory sqlSessionFactory =  SqlSessionUtil.getSqlSessionFactory();
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    System.out.println(sqlSession==sqlSession2);
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        System.out.println(user);

        sqlSession.close();

        System.out.println("=============================================");
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.getUserById(1);
        System.out.println(user2);

        System.out.println(user == user2);
    } finally {
    
    
        sqlSession2.close();
    }
}

运行结果:
false
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.5
User [id=1, username=管理员, password=123456]
true

6.2.2 解决二级缓存不生效问题

以6.2.1章节的配置和代码为基础:

SqlSessionUtil.java如下:

public class SqlSessionUtil {
    
    
	public static SqlSession getSqlSession() {
    
    
		SqlSession sqlSession = null;
		// 此处可以不使用try抛异常,SqlSessionFactoryBuilder().build()方法中有reader流关闭的操作
		try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml");) {
    
    
			SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
			sqlSession = sqlSessionFactory.openSession();
		} catch (IOException e) {
    
    
			e.printStackTrace();
		}
		if (sqlSession != null) {
    
    
			return sqlSession;
		} else {
    
    
			throw new NullPointerException("sqlSession为空");
		}
	}
}

CachedTest.java

@Test
public void testL5Cache() {
    
    
    // 不在同一个SqlSessionFactory是没有办法实现二级缓存的
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
    System.out.println(sqlSession==sqlSession2);
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        System.out.println(user);

        sqlSession.close();
        
        System.out.println("=============================================");
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.getUserById(1);
        System.out.println(user2);

        System.out.println(user == user2);
    } finally {
    
    
        sqlSession2.close();
    }
}

运行结果:
false
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
false

问题描述:
在MyBatis中,不同SqlSession作用域中开启了两个相同的查询操作。但是在控制台的输出中一直显示没有命中缓存,持续进行SQL查询操作。

问题排查:
1.是否在mybatis-conifg.xml中开启了全局缓存的设置。
2.是否在mapper.xml文件中打开了cache设置。
3.Bean对象是否可以序列化
上述步骤才可以打开二级缓存,现在在控制台中已经有Cache Hit Ratio 0的输出,证明二级缓存已经打开,但是由于SQL的相关问题,导致的缓存没有命中。继续排查……
4.SQL 语句是否一致
5.是否在新的SQL查询之前没有关闭上一个SqlSession,导致命中了一级缓存而不需要查询二级缓存

最终结论:
二级缓存存在于 SqlSessionFactory 生命周期中

​ 虽然二级缓存针对的对象是一个Mapper对象,只要是针对同一个Mapper的操作,都可以实现二级缓存。但是前提是操作必须在同一个SqlSessionFactory 中进行。
​ 在测试的时候,SqlSessionUtil.getSqlSession()每次都创建了一个SqlSessionFactory,实例化了多个SqlSessionFactory这样在后续的SQL操作中,是不可能命中二级缓存的。

代码修改如下:

SqlSessionUtil.java 新增getSqlSessionFactory方法

public static SqlSessionFactory getSqlSessionFactory() {
    
    
		// 此处可以不使用try抛异常,SqlSessionFactoryBuilder().build()方法中有reader流关闭的操作
		try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml");) {
    
    
			return new SqlSessionFactoryBuilder().build(reader);
		} catch (IOException e) {
    
    
			e.printStackTrace();
		}
		return null;
	}

CacheTest.java测试代码如下:

/**
 * 测试二级缓存
 */
@Test
public void testL6Cache() {
    
    
    SqlSessionFactory sqlSessionFactory =  SqlSessionUtil.getSqlSessionFactory();
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    System.out.println(sqlSession==sqlSession2);
    try {
    
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        System.out.println(user);

        sqlSession.close();

        System.out.println("=============================================");
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.getUserById(1);
        System.out.println(user2);

        System.out.println(user == user2);
    } finally {
    
    
        sqlSession2.close();
    }
}

运行结果:
false
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT id, username, password FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <==    Columns: id, username, password
TRACE [main] - <==        Row: 1, 管理员, 123456
DEBUG [main] - <==      Total: 1
User [id=1, username=管理员, password=123456]
=============================================
DEBUG [main] - Cache Hit Ratio [com.xiangty.mapper.UserMapper]: 0.5
User [id=1, username=管理员, password=123456]
true

问题解决。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPA06dON-1614513937815)(C:\Users\xiangty\Desktop\s8.jpg)]

如果文档中有任何问题,可以直接联系我,便于我改正和进步。希望文档对您有所帮助。文档中代码GitHub地址:https://gitee.com/xiangty1/learn-MyBatis/

猜你喜欢

转载自blog.csdn.net/qq_33369215/article/details/114239049