mybatis提供了查询缓存功能,用于减轻数据压力,提高数据库性能。mybatis提供了一级缓存和二级缓存,如下图所示:
从上图可以看出:
一级缓存是存在于一个sqlSession中的,而sqlSession就是操作数据库的一个会话对象,在对象中实际存储了一个hashMap的数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据互不影响。Mybatis默认开启一级缓存。
二级缓存是mapper级别的缓存,当多个sqlSession操作同一个mapper配置中的SQL语句时,多个sqlSession可以共用二级缓存,二级缓存是跨sqlSession的。
>一级缓存
mybatis一级缓存的作用域是同一个sqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。mybatis默认开启一级缓存。
如下图所示:
(1)第一次执行查询id为1的用户的信息操作时,先在缓存中查找,如果没有,则执行数据库查询操作,并将从数据库中查询到的用户数据信息,并放入到一级缓存。mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象。
(2)如果sqlSession执行insert、update、delete等操作commit提交后会清空一级缓存,这样做的目的是为了让缓存中存储的数据是最新的信息,避免脏读。
(3)第二次执行查询id为1的用户的信息操作时,先去缓存中查找,如果缓存中有,则直接从缓存中获取用户信息。
测试代码:
@Test
public void queryUserMapper() throws Exception{
Integer userId=1;
UserMapper mapper=session.getMapper(UserMapper.class);
User user = mapper.queryUserById(userId);
System.out.println(user);
User user2 = mapper.queryUserById(userId);
System.out.println(user2);
session.close();
}
Debug日志:只有第一次执行时,对数据库发出了sql请求;
DEBUG [main] - ==> Preparing: select * from user where user_id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
当两次查询中间插入事务操作:
@Test
public void queryUserMapper() throws Exception{
Integer userId=1;
UserMapper mapper=session.getMapper(UserMapper.class);
User user = mapper.queryUserById(userId);
System.out.println(user);
User user2=new User(null, "xinxinxiangrong", "1234598", "1", "huang", "1789999999", "山东威海", "0");
mapper.insertUser(user2);
session.commit();
User user3 = mapper.queryUserById(userId);
System.out.println(user3);
session.close();
}
Debug日志:向数据库发出了两次查询sql
DEBUG [main] - ==> Preparing: select * from user where user_id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
DEBUG [main] - ==> Preparing: insert into user values(?,?,?,?,?,?,?,?)
DEBUG [main] - ==> Parameters: null, xinxinxiangrong(String), 1(String), 1234598(String), huang(String), 0(String), 1789999999(String), 山东威海(String)
DEBUG [main] - <== Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@769e7ee8]
DEBUG [main] - ==> Preparing: select * from user where user_id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
注意:在正式开发中,是将mybatis和Spring进行整合开发,事务是在service层进行控制,一个service方法中包含很多mapper方法的调用。
在service方法开始执行时,就会开启事务,在开启事务的过程中就会创建SqlSession对象,因此期间不管是调用哪个mapper的方法,都是走一级缓存,有就从缓存中取,没有就从数据库中查询。方法结束时,事务关闭,同时清空一级缓存。
因此在同一service方法中是走一级缓存,如果是两次调用service方法,不走一级缓存,因为方法结束,SqlSession也就关闭,一级缓存就会被清空。
>二级缓存
在mybatis中允许多个SqlSession对象共享一个缓存区域,只不过这个缓存区域并一定在内存中,也可能是存储硬盘空间内,这个共享区域就是mybatis的二级缓存。mybatis同样适用hashMap这种数据结构来存储二级缓存中保存的数据。 如下图所示:
从上图中可以看出,mybatis的二级缓存是根据mapper配置文件的namespace命名空间进行划分的,相同namespace的查询数据操作放在同一个缓存区中。通常开发中,一个Mapper对应一个功能模块,即用户的查询操作的数据是放在UserMapper的缓存区中,订单查询操作的数据是放在OrderMapper的缓存区中。
如果两个用户的SqlSession会话都是执行同一个UserMapper接口中的方法,并且都去查询用户数据,每次查询都会先从缓存区中查找,如果找不到从数据库中查询,查询到的数据写入到二级缓存。
任何一个用户的SqlSession 执行insert、update、delete等操作commit提交后都会清空该mapper下的二级缓存区域。
在mybatis中二级缓存是默认关闭的,如果要开启mybatis的二级缓存,配置如下:
(1)在SqlMapConfig.xml全局配置文件中加入setting信息:
(2)在需要开启的二级缓存的mapper.xml中加入cache标签:
只要加入cache标签就表示开启二级缓存
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。因为mybatis实现的二级缓存,数据并不一定存储在内存中,也有可能是其他位置,比如硬盘、网络空间等。
在SqlSession会话中,如果会话没有结束,数据只会存储于一级缓存中,如果此SqlSession的会话结束并且此命名空间的mapper开启了二级缓存,这时数据才会写入到二级缓存中。
测试程序:
@Test
public void queryUserMapper() throws Exception{
Integer userId=1;
SqlSession session1=factory.openSession();
UserMapper mapper1=session1.getMapper(UserMapper.class);
User user1 = mapper1.queryUserById(userId);
System.out.println(user1);
session1.close();
SqlSession session2=factory.openSession();
UserMapper mapper2=session2.getMapper(UserMapper.class);
User user2 = mapper2.queryUserById(userId);
System.out.println(user2);
session2.close();
}
Debugr日志:仅发起了一次Sql查询
DEBUG [main] - ==> Preparing: select * from user where user_id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75329a49]
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75329a49]
DEBUG [main] - Returned connection 1966250569 to pool.
DEBUG [main] - Cache Hit Ratio [com.langsin.mapper.UserMapper]: 0.5
User [userId=1, userName=zhangsan, password=123456, roleCode=plain, cname=张三, telphone=17864195335, address=山东济南, isLogin=0]
注意:二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,否则报错: