#近来在研究Mybatis的源码,怕忘记,冒个泡。
Mybatis 是一个半自动的持久层框架,说白了还是JDBC封装后的一个框架,方便老铁们使用。
两种开发方式:传统的sqlsession开发,和mapper代理。都是一个样,最后都是sqlsession去操作。学完一个东西以后,一定能自己问自己几个问题,那就是学到了点东西,就是进步:
1、${}、#{}是如何解析的?
2、参数时如何设置的?
3、mybatis应用了哪些设计模式?具体说说源码
4、mybatis的四大组建?
Mybatis 的使用,主要有两个流程:
1、解析流程
String location="sqlmapConfig.xml";
InputStream is=Resource.getResourceAsStream(location);
SqlsessionFactory sqlsessionFactory=new SqlSessionFactoryBuilder().build(is);
额....似乎就完成了解析的全过程。好吧,进去看看具体的解析流程。
![](https://img.haomeiwen.com/i8153949/3280eeb6ac717a90.png)
XMLConfigBuilder : 解析全局配置文件
![](https://img.haomeiwen.com/i8153949/545653c3d428625e.png)
Configuration:全局配置对象
而在初始化XMLConfigBuilder,会实例化Configuration对象,做一些初始化的操作。并且全局配置文件、映射文件都会被解析到Configuration对象中。
![](https://img.haomeiwen.com/i8153949/3b5a5620be414b94.png)
XMLMapperBuilder: 解析映射文件
在XmlConfiguBuilder在解析配置文件的时候,调用mapperElement(root.evalNode("mappers"));对配置文件的mappers节点进行了解析。
![](https://img.haomeiwen.com/i8153949/b5b0702434fbe640.png)
parse()代码如下
![](https://img.haomeiwen.com/i8153949/7465563d8a8b2ddf.png)
解析mapper标签下的子标签,一 一 对应:
![](https://img.haomeiwen.com/i8153949/9d0911dcb8486628.png)
我们看看buildStatementFromContext 解析 crudl 语句。
![](https://img.haomeiwen.com/i8153949/2b9bf027af85d3d5.png)
XMLStatementBuilder : 解析crud标签
XMLStatementBuilder 用于解析crud 标签,并将每一个crud标签封装到一个mappedStatement对象中,然后使用Configuration.addMappedStatement (statment) (中间跳过了MapperBuilderAssistant对象) ;这些操作都是在 parseStatementNode()方法中完成的。
parseStatementNode():负责即解析sql节点
方法中:this.builderAssistant.addMappedStatement(...)内部将mappedstatement 添加到Configuration。
这里有个疑问,就是sql文本信息是如何解析的呢?带着这个疑问我看了源码,发现
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
langDriver是个接口它只有一个实现类:XMLLanguageDriver,
![](https://img.haomeiwen.com/i8153949/9ea85c28e2096c8e.png)
我们来看看这个类的createSqlSource做了什么。
![](https://img.haomeiwen.com/i8153949/285c042cae4a2015.png)
它创建了XMLScriptBuilder对象,并返回parseScriptNode()的执行结果,也就是SqlSource 。
XMLScriptBuilder:负责解析mapper文件中的每个<select/>,<insert/>,<update/>,<delete/>节点内的SQL字符串(其中可能包含动态SQL部分,诸如<if/>,<where/>等) 。
parseScriptNode会根据是否动态sql来生成不同的sqlSource,如图:
![](https://img.haomeiwen.com/i8153949/ac8f9596bea1f3d9.png)
此处我们看看RawSqlSource,在实例化RawSqlSource的时候,实例化了SqlSourceBuilder的对象,并且调用了parse方法:
![](https://img.haomeiwen.com/i8153949/3ba1f26adce88ecd.png)
具体看一下parse方法做了什么:
![](https://img.haomeiwen.com/i8153949/306bb7e2c9c7e526.png)
GenericTokenParser 定义了三个字段,分别为openToken(开始标记)、closeToken(结束标记)、handler(标记处理器),处理了“#{”,“}”,生成了新的sql字符串。(${}也是这个类进行处理),但是真正处理占位符的是这个handler,我们看看这个handler解析器类是如何操作的:
handler(TokenHandler)是一个接口,在实际处理中根据不同的情况有不同的实现类,比如说处理“${}”的实现
DynamicCheckerTokenParser,它的handleToken方法返回的是一个null.
![](https://img.haomeiwen.com/i8153949/b37d5affb9e1248e.png)
我们再看看"#{}"的处理handle:ParameterMappingTokenHandler,它是在SqlSourceBuilder中的parse():
![](https://img.haomeiwen.com/i8153949/899b2afac250ec7d.png)
它的handleToken实现如下:
![](https://img.haomeiwen.com/i8153949/a14c91cbf59ffb69.png)
可以看出它把参数放到 List<parameterMappings>parameterMappings的集合中,然后返回一个“?”,这也是为什么预编译语句后的参数值是个“?”。new StaticSqlSource(configuration, sql, handler.getParameterMappings());把sql语句和参数值给放到StaticSqlSource中返回。最终在DefaultParameterHandler中给设置进参数。
参数总结:
“${}” 的处理在TextSqlNode中,如果是简单类型数据,直接取值,如果是复杂类型的使用OGNL方式取值,当场替换为实际参数值。
“#{}” 的处理在SqlSourceBuilder的parse中,使用占位符(?)替换,最后在设置参数的时候使用Mybatis的MetaObject取值。
SqlSource:根据传入的参数对象,动态计算出这个BoundSql
![](https://img.haomeiwen.com/i8153949/ca6bb198f6627307.png)
SqlSource最常用的实现类是DynamicSqlSource,下面是getBoundSql的实现:
![](https://img.haomeiwen.com/i8153949/69044f003c29ee29.png)
SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的<if />节点的计算,是由SqlSource对象完成的。
最终存储在MappedStatement中
![](https://img.haomeiwen.com/i8153949/9023e55338d1e1bb.png)
BoundSql:一个BoundSql对象,代表了一次sql语句的实际执行
![](https://img.haomeiwen.com/i8153949/876ec13bd600887d.png)
// 进行 #{ } 和 ${ } 替换完毕之后的结果sql, 注意每个 #{ }替换完之后就是一个 ?
private String sql;
// 这里的parameterMappings列表参数里的item个数, 以及每个item的属性名称等等, 都是和上面的sql中的 ? 完全一一对应的.private List<ParameterMapping> parameterMappings;
// 用户传入的数据
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
2、执行流程
以上解析流程就是到获取sqlsession,执行流程从sqlsession.selectList 开始。
Sqlsession sqlsession=SqlsessionFactory.openSession();
openSession的时候,返回openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false)的值,openSessionFromDataSource方法如下:
![](https://img.haomeiwen.com/i8153949/778b6fdf1e3c06f4.png)
openSession的时候,会根据枚举 execType 的值(configuration.getDefaultExecutorType()),创建 Executor(默认是SIMPLE)
protected ExecutorTypedefaultExecutorType = ExecutorType.SIMPLE;
![](https://img.haomeiwen.com/i8153949/afad74ee9e8efe20.png)
默认是实例化SimpleExecutor,但是如果配置缓存执行器,那么缓存执行器会覆盖。Executor 接口有两个实现类:BaseExecutor、CachingExecutor,结构图如下:
![](https://img.haomeiwen.com/i8153949/36cb3de69be5fd39.png)
然后返回 DefautSqlSession() 对象,进行select 操作,最终调用如下代码:
sqlsession.selectList(statement, parameter, rowBounds);
![](https://img.haomeiwen.com/i8153949/e5c38fb3d3df5298.png)
执行executor.query,这里以SimpleExecutor 为例,在调用executor.query的时候会调用SimpleExecutor 的doQuery:
![](https://img.haomeiwen.com/i8153949/0837268df8eef818.png)
![](https://img.haomeiwen.com/i8153949/5b1c089078c0bc44.png)
===扩展开始===
statementHandler 接口有两个实现类:
BaseStatementHandler:有三个子类
SimpleStatementHandler:
PreparedStatementHandler:
CallableStatementHandler:
RoutingStatementHandler:在这个类里面 根据配置的statementType创建statementHandler。默认是PreparedStatementHandler
statementHandler 有如下方法:
Statement prepare(Connection connection):创建Statement对象,即该方法会通过Connection对象创建Statement对象。
void parameterize(Statement statement):对Statement对象参数化,特别是PreapreStatement对象。
void batch(Statement statement):批量执行SQL。
int update(Statement statement):更新操作。
< E> List< E> query(Statement statement, ResultHandler resultHandler):查询操作。
BoundSql getBoundSql():获取SQL语句。
ParameterHandler getParameterHandler():获取对应的参数处理器。
===以上扩展结束,接上图开讲===
默认创建PreparedStatementHandler,其实这几个子类在实例化的时候,调用父类的构造函数,我们看看父类 BaseStatementHandler 的够着函数:
![](https://img.haomeiwen.com/i8153949/b6142c872bbe8c64.png)
typeHandlerRegistry:类型注册器,这个类定义了mybatis 的数据类型,与sql的对应类型。可以去看看。
看最底部:
this.parameterHandler =configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler =configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
这里引出来,四大组建的另外两个。
3、其它
selectlist 默认显示多少条数据?
selectList默认会调用:selectList(String statement, Object parameter, RowBounds rowBounds),如果没有传入分页对象,默认是offset:0;limit:Integer.MAX_VALUE;
网友评论