关于MyBatis中一级缓存的一些思考(一)

关于MyBatis中一级缓存的一些思考(一)

Mybatis的默认设置了两级缓存:一级缓存(本地缓存)和二级缓存(全局缓存)。其中一级缓存是SqlSession级别的缓存,二级缓存是在namespace级别下的缓存(全局配置文件中默认会开启二级缓存,但是仍然需要在相对应的XXXmapper.xml中配置<cache></cache>标签)。
众所周知,一级缓存在一次会话中是会一直开启的,其内部实现原理是把查询到的数据存放在一个Map中。设置缓存主要的目的是为了节省系统的开销,待下次需要查询相同的数据时不需要再向数据库中发生sql语句,而是直接从缓存中取得数据。
但是会有几个让一级缓存失效的情况:
* 1.sqlSession不同
* 2.sqlSession相同,但是查询条件不同(因为当前一级缓存中还没有这个数据)
* 3.sqlSession相同,但是两次查询期间执行了增删改操作(因为这次增删改就可能对当前缓存中的数据有影响);
* 4.sqlSession相同,但是手动清除了一级缓存(一级缓存中的东西被清空了);

这些情况除了第一种情况是因为会话不同之外,其他的归根到底都是当Mybatis认为数据可能发生了变化时,需要重新向数据库发送sql去查询最新的数据。

但是我自己对这些情况做出了一些思考,考虑到两种情况下,会不会向数据库发送sql进行查询:
* 1.如果在后台数据库直接改数据库,一级缓存会不会失效?
* 2.针对上边的第3条,如果别的会话(SqlSession)执行了增删改,此时当前的会话的一级缓存会不会失效?

对于第1种情况,应该不会失效,因为一级缓存是SqlSession会话级别的缓存,在这次会话没有结束之前,如果MyBatis没有认为数据库发生了变化,对于同样查询条件的语句来说,应该都是去缓存中取数据。由于直接在后台的数据库中改数据,并不会给会话带来什么反馈,告诉会话数据库的数据发生了变化,需要重新查询数据更新缓存了。另外,在实际的应用中,能够直接操作数据库需要很高的权限,一般也不会发生这种情况。

主要来看一下第2种情况,这种情况跟上边第3条很相似,按照推理,别的会话对数据库做出了增删改的操作,应该让Mybatis认为当前存在的所有会话的一级缓存中的数据发生了变化,所以也应该需要去重新查询数据了。这里我们通过代码来看一下:

	@Test
	public void testFirstLevelCache() throws Exception {
		//得到sqlSession工厂对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		//开启和数据库的一次会话
		SqlSession session = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
			//第一次查询这个数据,这里会将其放在缓存中
			Employee emp01 = mapper.getEmpById(1);
			System.out.println("emp01: " + emp01);
			//开启和数据库的另一次会话
			SqlSession session2 = sqlSessionFactory.openSession();
			EmployeeMapper mapper2 = session2.getMapper(EmployeeMapper.class);
			//第二个会话的mapper进行插入操作
			mapper2.addEmp(new Employee(null, "testCache", "[email protected]", "1"));
			session2.commit();
			session2.close();
			//重新查询这个数据
			Employee emp02 = mapper.getEmpById(1);
			System.out.println("emp02: " + emp02);	
			System.out.println(emp01 == emp02);
		} finally {
			session.close();
		}
	}

其中getSqlSessionFactory()是我自己封装好的方法,内容如下:

	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

这段代码的意思是开启两段会话,让第一段会话先查询一个数据emp01,这里会把它放在一级缓存中,然后第二个会话对数据库进行增删改的操作让数据库发生变化,最后再用第一个会话发送查询请求获得第二个数据emp02,然后通过"=="操作符看看这两个对象是否是同一个。经过运行,通过日志我们来查看结果:
在这里插入图片描述
从日志中可以看出,这里只向数据库发送了两条sql语句,分别是第一个会话第一次查询时的sql以及第二个会话增加数据时的sql。但是并没有看到第一个会话第二次查询时发送sql,这应该是直接在一级缓存中取的数据,从最后输出true也能证明这一点。这就与我们之前的猜想不同了,很奇怪。

总结:后来思考认为,这可能是由于数据库的隔离级别所引起的情况吧。不过最大的原因可能还是Mybatis设置的原因,因为两个会话之间是不存在什么交互的,应该是处在两个独立的线程中实现各自的功能。但是这种情况下为了数据的安全按道理应该采取上边思考的措施,否则会出现类似于脏读的现象(但不是脏读,情况不一样)。这里没有搞清楚暂时还不能妄下结论,还需要后续的学习去继续深挖这个问题。待后边知识储备完善以后,再回来继续完善这篇文章。

猜你喜欢

转载自blog.csdn.net/sinat_38848255/article/details/107722026