一级缓存
1)首先做个测试,创建一个mapper配置文件和mapper接口,我这里用了最简单的查询来演示。
<mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper">
<select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User">
SELECT * FROM user
</select>
</mapper>
public interface UserMapper {
List<User> findUsers() throws Exception;
}
然后编写一个单元测试:
public class UserMapperTest{
SqlSession sqlSession = null;
@Before
public void setUp() throws Exception{
//通过配置文件获取数据库连接信息
Reader reader = Resources.getResourceAsReader("cn/elinzhou/mybatisTest/config/mybatis.xml");
//通过配置信息构建一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//通过sqlSessionFactory打开一个数据库会话
sqlSession = sqlSessionFactory.openSession();
}
@Test
public void testFindUsers() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findUsers();
System.out.println(users);
}
}
运行,可以看到控制台输出(先配好log4j)为类似如下图日志:(日志说明了该操作执行的sql语句已经查询的内容,最后一行是我手动通过System.out.println输出的结果)
然后再加一条语句 users = userMapper.findUsers();
,单元测试代码如下:
@Test
public void testFindUsers() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findUsers();
users = userMapper.findUsers();
System.out.println(users);
}
也就是执行完 userMapper.findUsers();
后立刻再执行一遍 userMapper.findUsers();
可以想象,其实这两个操作执行的sql是完全相同的,而且在这期间没有对数据库进行过其他操作。执行该单元测试,发现打印的日志跟上面执行的sql是完全相同,也就是执行第二次 userMapper.findUasers();
操作时没有对数据库进行查询,那么得到的数据是从哪里来的?答案是一级缓存。
MyBatis一级缓存是指在内存中开辟一块区域,用来保存用户对数据库的操作信息(sql)和数据库返回的数据,如果下一次用户再执行相同的请求,那么直接从内存中读取数据而不是从数据库读取。
其中数据的生命周期有两个影响因素:
1)对sqlSession执行commit操作时:
对sqlSeesion执行commit操作,也就意味着用户执行了update、delete等操作,那么数据库中的数据势必会发生变化,如果用户请求数据仍然使用之前内存中的数据,那么将读到脏数据。所以在执行sqlSession操作后,会清除保存数据的HashMap,用户在发起查询请求时就会重新读取数据并放入一级缓存中了。
在下面代码中增加 sqlSession.commit();
后,再执行单元测试:
@Test
public void testFindUsers() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findUsers();
sqlSession.commit();
users = userMapper.findUsers();
System.out.println(users);
}
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Preparing: SELECT * FORM user
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Parameters:
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - <== Total: 8
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ⇒ Preparing: SELECT * FROM user
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Parameters:
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - <== Total: 8
[User{address='null',id=1,name='null',birthday=null,sex=2},User{address='null',id=1,name='null',birthday=null,sex=2},User{address='null',id=1,name='null',birthday=null,sex=2}...]
Process finished with exit code 0
上述测试就是在第一查询完成后执行了commit操作,再进行查询。与之前的测试不同的是,这次测试控制台打印了两组结果,说明在commit之后MyBatis对数据重新进行了查询。
2)关闭sqlSession:
一般在MyBatis集成Spring时,会把SqlSessionFactory设置为单例注入到IOC容器中,不把SqlSession也设置为单例的原因是SqlSession是线程不安全的,所以不能为单例。那也就意味着其实是有关闭SqlSession的过程的。其实,对于每一个service中的SqlSession是不同的,这是通过MyBatis-Spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建SqlSession自动注入到service中的。而一级缓存的设计是每个SqlSession单独使用一个缓存空间,不同的SqlSession是不能相互访问数据的。当然,在SqlSession关闭后,其中数据自然被清空。
特此警告!!!!当MyBatis与Spring整合后,如果没有事务,一级缓存是失效的!一级缓存是失效的!一级缓存是失效的!
原因就是两者结合后,SqlSession如果发现当前没有事务,那么执行一个mapper方法后,SqlSession就被关闭了。如果需要维持一级缓存的可用性,有两种途径:
- 添加事务
- 使用二级缓存
二级缓存(也可以用redis代替)
在使用二级缓存之前,先测试之前提到过的关闭SqlSession后会清空缓存的问题,把Junit代码修改一下
@Test
public void testFindUsers() throws Exception{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findUsers();
//关闭SqlSession
sqlSession.close();
//通过SqlSessionFactory创建一个新的SqlSession
sqlSession = SqlSessionFactory.openSession();
//获取mapper对象
userMapper = sqlSession.getMapper(UserMapper.class);
users = userMapper.findUsers();
System.out.println(users);
}
这段代码在第一次查询完毕后关闭SqlSession,然后创建新的SqlSession和Mapper来重新执行一次查询操作,可以遇见,执行结果如图:
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Preparing: SELECT * FORM user
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Parameters:
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - <== Total: 8
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ⇒ Preparing: SELECT * FROM user
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - ==> Parameters:
2015-06-30 13:56:27,070 [main] DEBUG [cn.elinzhou.mybatisTest.mapper.UserMapper.findUsers] - <== Total: 8
[User{address='null',id=1,name='null',birthday=null,sex=2},User{address='null',id=1,name='null',birthday=null,sex=2},User{address='null',id=1,name='null',birthday=null,sex=2}...]
Process finished with exit code 0
说明关闭了SqlSession后的确把之前的缓存数据清空了,之后再执行同样的查询操作也会再访问一遍数据库。
为了解决这个问题,需要使用二级缓存。