#近来在研究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);
额....似乎就完成了解析的全过程。好吧,进去看看具体的解析流程。
XMLConfigBuilder : 解析全局配置文件
Configuration:全局配置对象
而在初始化XMLConfigBuilder,会实例化Configuration对象,做一些初始化的操作。并且全局配置文件、映射文件都会被解析到Configuration对象中。
XMLMapperBuilder: 解析映射文件
在XmlConfiguBuilder在解析配置文件的时候,调用mapperElement(root.evalNode("mappers"));对配置文件的mappers节点进行了解析。
parse()代码如下
解析mapper标签下的子标签,一 一 对应:
我们看看buildStatementFromContext 解析 crudl 语句。
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,
我们来看看这个类的createSqlSource做了什么。
它创建了XMLScriptBuilder对象,并返回parseScriptNode()的执行结果,也就是SqlSource 。
XMLScriptBuilder:负责解析mapper文件中的每个,
parseScriptNode会根据是否动态sql来生成不同的sqlSource,如图:
此处我们看看RawSqlSource,在实例化RawSqlSource的时候,实例化了SqlSourceBuilder的对象,并且调用了parse方法:
具体看一下parse方法做了什么:
GenericTokenParser 定义了三个字段,分别为openToken(开始标记)、closeToken(结束标记)、handler(标记处理器),处理了“#{”,“}”,生成了新的sql字符串。(${}也是这个类进行处理),但是真正处理占位符的是这个handler,我们看看这个handler解析器类是如何操作的:
handler(TokenHandler)是一个接口,在实际处理中根据不同的情况有不同的实现类,比如说处理“${}”的实现
DynamicCheckerTokenParser,它的handleToken方法返回的是一个null.
我们再看看"#{}"的处理handle:ParameterMappingTokenHandler,它是在SqlSourceBuilder中的parse():
它的handleToken实现如下:
可以看出它把参数放到 List
参数总结:
“${}” 的处理在TextSqlNode中,如果是简单类型数据,直接取值,如果是复杂类型的使用OGNL方式取值,当场替换为实际参数值。
“#{}” 的处理在SqlSourceBuilder的parse中,使用占位符(?)替换,最后在设置参数的时候使用Mybatis的MetaObject取值。
SqlSource:根据传入的参数对象,动态计算出这个BoundSql
SqlSource最常用的实现类是DynamicSqlSource,下面是getBoundSql的实现:
SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的
最终存储在MappedStatement中
BoundSql:一个BoundSql对象,代表了一次sql语句的实际执行
// 进行 #{ } 和 ${ } 替换完毕之后的结果sql, 注意每个 #{ }替换完之后就是一个 ?
private String sql;
// 这里的parameterMappings列表参数里的item个数, 以及每个item的属性名称等等, 都是和上面的sql中的 ? 完全一一对应的.private List
// 用户传入的数据
private Object parameterObject;
private Map
private MetaObject metaParameters;
2、执行流程
以上解析流程就是到获取sqlsession,执行流程从sqlsession.selectList 开始。
Sqlsession sqlsession=SqlsessionFactory.openSession();
openSession的时候,返回openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false)的值,openSessionFromDataSource方法如下:
openSession的时候,会根据枚举 execType 的值(configuration.getDefaultExecutorType()),创建 Executor(默认是SIMPLE)
protected ExecutorTypedefaultExecutorType = ExecutorType.SIMPLE;
默认是实例化SimpleExecutor,但是如果配置缓存执行器,那么缓存执行器会覆盖。Executor 接口有两个实现类:BaseExecutor、CachingExecutor,结构图如下:
(SimpleExecutor 无论sql是否一样,都会进行预编译.....)
然后返回 DefautSqlSession() 对象,进行select 操作,最终调用如下代码:
sqlsession.selectList(statement, parameter, rowBounds);
执行executor.query,这里以SimpleExecutor 为例,在调用executor.query的时候会调用SimpleExecutor 的doQuery:
RoutingStatementHandler 是statementHandler的实现类
===扩展开始===
statementHandler 接口有两个实现类:RoutingStatementHandler、BaseStatementHandler
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 的够着函数:
typeHandlerRegistry:类型注册器,这个类定义了mybatis 的数据类型,与sql的对应类型。可以去看看。
看最底部:
this.parameterHandler =configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler =configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
这里引出来,四大组建的另外两个。
3、mapper 代理
通过mapperProxy 代理的方式,执行crud。
UserDao userMapper = sqlSession.getMapper(UserDao.class);
看看 defaultSqlsession 的 getMapper
再进去看看configuration 的getMapper(Class type, SqlSession sqlSession):
mapperRegistry啥玩意?
上面几个哥们什么事情都不做, 都交给 mapperProxyFactory 做苦逼的事情。
mapperProxyFactory.newInstance(sqlSession); 生产代理实例
创建 MapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache) 对象,然后将 MapperProxy 在 newInstancec()方法中去代理Dao接口。注意看invoke方法:
执行 exexute
根据sql类型的不同来执行sql语句,能看到insert,delete和update,都是先解析入参,在调用了sqlSession的内置的方法,内置的方法前面已经讲过,就不重复说了。看select,select也是一样,如果返回是void,那么result就是null,判断方法返回的类型,剩下的起始和调用内置方法是一样的。
解析参数,然后调用sqlSession的selectList
4、其它
SqlCommand:
selectlist 默认显示多少条数据?
selectList默认会调用:selectList(String statement, Object parameter, RowBounds rowBounds),如果没有传入分页对象,默认是offset:0;limit:Integer.MAX_VALUE;