MyBatis的工作原理
MyBatis的工作原理
- 加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MapperStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select | update | delete | insert>标签项。
- SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
- SqlSession对象完成和数据库的交互:
a、用户程序调用mybatis接口层api(即Mapper接口中的方法)
b、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象
c、通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象,使用Paramterhandler填充参数,使用statementHandler绑定参数。
d、JDBC执行sql。
e、借助MappedStatement中的结果映射关系,使用ResultSetHandler将返回结果转化成HashMap、JavaBean等存储结构并返回。
f、关闭sqlsession会话。
MyBatis的主要成员
Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
以上主要成员在一次数据库操作中基本都会涉及,在SQL操作中重点需要关注的是SQL参数什么时候被设置和结果集怎么转换为JavaBean对象的,这两个过程正好对应StatementHandler和ResultSetHandler类中的处理逻辑。
Mybatis加载机制
MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式加载与深度延迟加载
- 直接加载: 执行完对主加载对象的select语句,马上执行对关联对象的select查询。
- 侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的某个属性(该属性不是关联对象的属性)时,就会马上执行关联对象的select查询。
- 深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
Mybatis缓存机制
Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性
能。
Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存,如图:
- 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个
数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)
是互相不影响的。 - 二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语
句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存
(此图片引用自https://www.cnblogs.com/happyflyingpig/p/7739749.html)
说明:
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据
库查询用户信息,将查询到的用户信息存储到一级缓存中。 - 如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓
存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 - 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从
缓存中获取用户信息。 - Mybatis一级缓存默认是开启的,不需要配置
具体应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用:
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,是不走一级缓存的,因为mapper方法结束,
sqlSession就关闭,一级缓存就清空。
二级缓存
二级缓存是mapper(namespace)级别的。
下图是多个sqlSession请求UserMapper的二级缓存图解。
(此图片引用自https://www.cnblogs.com/happyflyingpig/p/7739749.html)
说明:
- 第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区
域内。 - 第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级
缓存内取结果。 - 如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会
清空该namespace下的二级缓存。
开启二级缓存
Mybatis默认是没有开启二级缓存,开启步骤如下:
- 在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
<!-- 开启二级缓存总开关 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在Mapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>
- 实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,比如说存储到文件系统中,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
public class Team implements Serializable {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
}
禁用二级缓存
默认二级缓存的粒度是Mapper级别的,但是如果在同一个Mapper文件中某个查询不想使用二级缓存的
话,就需要对缓存的控制粒度更细。
在select标签中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<select id="findUserById" parameterType="int" resultType="com.hsx.pojo.User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
刷新二级缓存
通过flushCache属性,可以控制select、insert、update、delete标签是否属性二级缓存
默认设置
- 默认情况下如果是select语句,那么flushCache是false。
- 如果是insert、update、delete语句,那么flushCache是true。
默认配置解读
- 如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
- 如果增删改语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下:
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User" useCache="true" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
应用场景
使用场景:
对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意事项:
在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
局限性
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决方法
此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。