🍅 Java学习路线:搬砖工的Java学习路线
🍅 作者:程序员小王
🍅 程序员小王的博客:https://www.wolai.com/wnaghengjie/ahNwvAUPG2Hb1Sy7Z8waaF
🍅 扫描主页左侧二维码,加我微信 一起学习、一起进步
🍅 欢迎点赞 👍 收藏 ⭐留言 📝
mybatis的缓存机制
一、缓存:缓存存储
1、现有的查询策略:
现有查询策略:每次查询都会链接访问数据库
存在的问题:每次都需要获取链接,释放连接资源,降低程序运行效率,解决方案使用缓存
2、使用缓存:
缓存:内存中的一块存储空间,用于存放多个用户反复查询数据,有了缓存之后
后续的查询数据都会直接从缓存中获取
3、使用缓存的好处:
减少每次使用链接的占用,提升查询效率,
提升程序的运行效率
4、使用缓存的缺点:
占用大量的内存资源(成本高)
5、缓存的机制:
以空间换时间(效率)
二、mybatis缓存
1、mybatis的缓存机制
(1)一级缓存 基于sqlSession的缓存【默认开启】
- 一级缓存:也称为本地缓存,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。
注意:查询语句最后都是关闭资源,如果不关闭资源数据会出现闪动
不关闭资源,第一次查询四条,第二次5条,SqlSession.close()关闭资源就是为了关闭sqlSession的缓存
一级缓存的特点:
1、查询第一次时,获取到数据写入一级缓存,再次查询时从缓存获取,不再执行sql语句
2、若当前SqlSession发生修改、增加、删除动作时,就会立即把当前缓存的所有数据清空
3、对于查询操作,只要SqlSession没有调用flush或者close方法,它就一直存在
注意:一次缓存造成查询数据结果闪动
(2)二级缓存:基于mapper的缓存(基于指定实体类的缓存)(末默认不开启)
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。(也可以说是,在同一namespace下,共享一块缓存空间,如果多个mapper (dao.xml)共享同一namesapce 则也共享一块缓存,二级缓存是跨sqlsession,多个sqlsession可以去二级缓存获取数据。即可以针对同一个dao接口或者同一个命名空间(namespace)创建多个SqlSession )
二级缓存特点:
1、只要发生增删改,就会将·同一命名空间(namespace)下的缓存清空
2、使用二级缓存实体类必须实现序列化,否则报错
3、使用查询语句,默认只写入一级缓存,只有调用close(),commit()方法,
才会将数据提交到二级缓存,其他的sqlsession才能拿到,不再执行sql语句
2、二级缓存 开启手工设置
1、在Mybatis框架中声明 使用二级缓存 在mybatis-config.xml中配置
mybatis-config.xml中
mybatis-config.xml:
<!-- 设置缓存-->
<settings>
<!--开启Mybatis二级缓存
cacheEnabled:启动mybatis二级缓存 值:布尔值 false:不启用 【默认值】 true :开启
-->
<setting name="cacheEnabled" value="true"/>
</settings>
注意:
2、在需要进行缓存的操作Mapper中指定使用缓存 在mapper文件中声明
<!--开启当前查询操作缓存-->
<cache></cache>
3、要缓存的实体类必须实现序列化接口(实体类中)
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private Integer age;
private Date birthday;
- 实体类实现序列化接口的原因:
数据的数据唤出:长时间不用的放到缓存文件里
数据的唤入:当再次使用时,
4、缓存测试
1、在mybatis工具类中重写一个执行sqlSession的方法:
作用:用于测试缓存,实际开发不使用
//mybatisUtil.java 工具类
public class MybatisUtil {
/**
* 线程绑定对象,保证是同一个对象
*/
private static final ThreadLocal<SqlSession> t1 = new ThreadLocal<>();
private static SqlSessionFactory factory;
static {
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
factory = new SqlSessionFactoryBuilder().build(is);
}
/**
* 创建SqlSession
*
* @return sqlSession
*/
private static SqlSession openSession() {
SqlSession sqlSession = t1.get();
if (sqlSession == null) {
sqlSession = factory.openSession();
t1.set(sqlSession);
}
return sqlSession;
}
/**
* 仅用于测试缓存 每次调用方法获取的都是全新的连接 实际开发不使用
* @return sqlSession
*/
public static SqlSession getSqlSession(){
return factory.openSession();
}
/**
* 调用功能方法
*
* @param clazz
* @return Object
*/
public static Object getMapper(Class clazz) {
return openSession().getMapper(clazz);
}
/**
* 关闭资源
*/
public static void close() {
if (openSession() != null) {
openSession().close();
}
t1.remove();
}
/**
* 提交事务,关闭资源
*/
public static void commit() {
//1、提交事务
commit();
//2、关闭资源
close();
t1.remove();
}
/**
* 回滚事务,关闭资源
*/
public static void rollback() {
//1、提交事务
openSession().rollback();
//2、关闭资源
close();
t1.remove();
}
}
- 工具类的sqlSession不开启线程绑定对象【这样每次查询都会创建一个sqlSession】
/**
* 仅用于测试缓存 每次调用方法获取的都是全新的连接 实际开发不使用
* @return sqlSession
*/
public static SqlSession getSqlSession(){
return factory.openSession();
}
2、测试
//注意:测试之前mybatis工具类调用的方法不能开启ThreadLocal线程绑定对象,否则调用的都是同一个sqlSession
//必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存区
@Test
public void CacheTest() {
//第一次id查询id为1的同学
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
UserDao mapper = sqlSession1.getMapper(UserDao.class);
User user = new User();
user.setId(1);
List<User> users = mapper.selectUserAll(user);
System.out.println("第一次id查询id为1的同学:");
for (User user1 : users) {
System.out.println(user1);
}
//必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存
MybatisUtil.close();
//第二次id查询id为1的同学
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
UserDao mapper2 = sqlSession1.getMapper(UserDao.class);
User user2 = new User();
user.setId(1);
List<User> users2 = mapper.selectUserAll(user);
System.out.println("第二次id查询id为1的同学:");
for (User user3: users2) {
System.out.println(user3);
}
//必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存
MybatisUtil.close();
}
3、注意事项:
1、注意:测试之前mybatis工具类调用的方法不能开启ThreadLocal线程绑定对象,
否则调用的都是同一个sqlSession
2、**必须关闭链接,不关闭放在一级缓存里,关掉才会放在二级缓存区中**
3、二级缓存执行机制
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
1、一级缓存与二级缓存调用顺序:
获取数据先去二级缓存获取,如果有得到,写入(更新)一级缓存**
若没有得到,再去一级缓存,如果仍没有,再去数据库**
优先级: 二级缓存--->一级缓存--->数据库**
三、缓存中的脏数据
1、出现脏数据的原因:
1、主要原因:
数据库增删改之后,缓存的数据和数据库中的数据不匹配
2、详细原因:
使用二级缓存,当有一个session发生修改(增删改)时,
将二级缓存清空了,然而另一个session缓存过以前的查询结果
(此时二级缓存没数据,去一级缓存却拿到了之前的),则可能产生脏数据。
如上图:
1、第一步sqlsession1 去查询id=1的数据,
并写入了sqlsession1的一级缓存与共同的二级缓存
2、第二步sqlsession2 去查询id=1的数据,
发现二级缓存有数据,就不执行sql,并将其写入sqlsession2的一级缓存
3、第三步sqlsession2执行**修改** id=1的数据,
清空了二级缓存与sqlsession2的一级缓存
4、第四步sqlsession1 再去查询id=1的数据,二级缓存没有,
但却从sqlsession1的一级缓存读到了数据,但这却是修改之前的,就产生了脏数据
2、脏数据解决方案
判断是否刷新缓存flushCache;
在默认的设置的select语句是不会刷新缓存的,insert/update/delte会刷新缓存。
3、Mybatis脏数据解决:
根据是否控制事务来判断是否刷新(清空)缓存:
SqlSession.commit()
SqlSession.rollback()
注意:
1、Mybatis会在事务控制时清空缓存,
2、后续功能查询一定不要控制事务(清空缓存) ,
3、增删改一定要控制事务
文末
👉在此,鸣谢:刘浩老师讲解
📌 作者:王恒杰
📃 更新: 2021.10.15
❌ 勘误: 无
📜 声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!
🍅 欢迎点赞 👍 收藏 ⭐留言 📝