美文网首页百年孤独
Mybatis 源码梳理

Mybatis 源码梳理

作者: 黑客军团_charles | 来源:发表于2020-10-10 15:31 被阅读0次

#近来在研究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);

额....似乎就完成了解析的全过程。好吧,进去看看具体的解析流程。

SqlSessionFactoryBuilder().build(inputstream) 源码截图  

XMLConfigBuilder : 解析全局配置文件

XMLConfigBuilder 的parse()方法

Configuration:全局配置对象

而在初始化XMLConfigBuilder,会实例化Configuration对象,做一些初始化的操作。并且全局配置文件、映射文件都会被解析到Configuration对象中。

Configuration初始化

XMLMapperBuilder: 解析映射文件

在XmlConfiguBuilder在解析配置文件的时候,调用mapperElement(root.evalNode("mappers"));对配置文件的mappers节点进行了解析。

mapperElement 创建 XmlmapperBuilder  解析映射文件

parse()代码如下

XMLMapperBuilder.parse() 解析映射文件

   解析mapper标签下的子标签,一 一 对应:

xml映射文件中 mapper 标签的子标签

我们看看buildStatementFromContext 解析 crudl 语句。 

XMLMapperBuilder创建XMLStatementBuilder   解析CRUD标签

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,

langDriver的实现类XMLLanguageDriver

我们来看看这个类的createSqlSource做了什么。

XMLLanguageDriver  实现langDriver  接口的 createSqlSource 方法

它创建了XMLScriptBuilder对象,并返回parseScriptNode()的执行结果,也就是SqlSource 。

XMLScriptBuilder:负责解析mapper文件中的每个<select/>,<insert/>,<update/>,<delete/>节点内的SQL字符串(其中可能包含动态SQL部分,诸如<if/>,<where/>等) 。

parseScriptNode会根据是否动态sql来生成不同的sqlSource,如图:

XMLScriptBuilder 的parseScriptNode方法

此处我们看看RawSqlSource,在实例化RawSqlSource的时候,实例化了SqlSourceBuilder的对象,并且调用了parse方法:

RawSqlSource的构造函数

具体看一下parse方法做了什么:

SqlSourceBuilder的parse

GenericTokenParser 定义了三个字段,分别为openToken(开始标记)、closeToken(结束标记)、handler(标记处理器),处理了“#{”,“}”,生成了新的sql字符串。(${}也是这个类进行处理),但是真正处理占位符的是这个handler,我们看看这个handler解析器类是如何操作的:

handler(TokenHandler)是一个接口,在实际处理中根据不同的情况有不同的实现类,比如说处理“${}”的实现

DynamicCheckerTokenParser,它的handleToken方法返回的是一个null.

${} 占位符处理方法

我们再看看"#{}"的处理handle:ParameterMappingTokenHandler,它是在SqlSourceBuilder中的parse():

"#{}"的处理类ParameterMappingTokenHandler

它的handleToken实现如下:

ParameterMappingTokenHandler的handleToken实现

可以看出它把参数放到 List<parameterMappings>parameterMappings的集合中,然后返回一个“?”,这也是为什么预编译语句后的参数值是个“?”。new StaticSqlSource(configuration, sql, handler.getParameterMappings());把sql语句和参数值给放到StaticSqlSource中返回。最终在DefaultParameterHandler中给设置进参数。

参数总结:

    “${}” 的处理在TextSqlNode中,如果是简单类型数据,直接取值,如果是复杂类型的使用OGNL方式取值,当场替换为实际参数值。

    “#{}” 的处理在SqlSourceBuilder的parse中,使用占位符(?)替换,最后在设置参数的时候使用Mybatis的MetaObject取值。

SqlSource:根据传入的参数对象,动态计算出这个BoundSql

SqlSource接口

SqlSource最常用的实现类是DynamicSqlSource,下面是getBoundSql的实现:

DynamicSqlSource  getBoundSql的实现

 SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的<if />节点的计算,是由SqlSource对象完成的。

最终存储在MappedStatement中

BoundSql:一个BoundSql对象,代表了一次sql语句的实际执行

BoundSql的属性

// 进行 #{ } 和 ${ } 替换完毕之后的结果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方法如下:

openSession 的时候执行代码

openSession的时候,会根据枚举 execType 的值(configuration.getDefaultExecutorType()),创建 Executor(默认是SIMPLE)

        protected ExecutorTypedefaultExecutorType = ExecutorType.SIMPLE;

configuration 创建executor

默认是实例化SimpleExecutor,但是如果配置缓存执行器,那么缓存执行器会覆盖。Executor 接口有两个实现类:BaseExecutor、CachingExecutor,结构图如下:

Executor 结构图

然后返回 DefautSqlSession() 对象,进行select 操作,最终调用如下代码:

        sqlsession.selectList(statement, parameter, rowBounds);

selectList 最终执行代码

    执行executor.query,这里以SimpleExecutor 为例,在调用executor.query的时候会调用SimpleExecutor 的doQuery:

configuration 创建StatementHandler configuration 根据statementType(默认 StatementType.PREPARED)

===扩展开始===

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 的够着函数:

BaseStatementHandler的构造函数

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;

相关文章

网友评论

    本文标题:Mybatis 源码梳理

    本文链接:https://www.haomeiwen.com/subject/ggiiwhtx.html