mybatis源码阅读简短体会
好!我是Whim,帝都的一个小小java,这篇博客主要是笼统的记录一下mybatis的主流程。当然,这里并不是,要上多少源码,一步一步解析清楚,而是简短的介绍一个,主要流程以及映射关系
显然,此框架是用来连接数据库,执行sql,处理返回值的。使用该框架当然是方便快捷,但是如果想清楚一下mybatis框架的设计以及可以读懂大概流程的话,JDBC 还是要深入理解一下的,因为只有理解了JDBC 的执行流程,才会对框架的设计有一个预想。
如果不用spring,单单用mybatis的话,你会先怎么使用呢?—>SqlSessionFactory,我们需要SqlSessionFactory来创建我们执行sql所需要的SqlSession,而创建SqlSessionFactory会用到我前面创建型设计模式上博客中的构建者模式,使用SqlSessionFactoryBuild来build出我们需要的SqlSessionFactory,下端代码是mybatis中SqlSessionFactoryBuild的build方法;
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder:用来解析XML配置文件
// 使用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
// 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
很多人会疑惑,上面的XMLConfigBuilder .parse()方法在做什么,这才是mybatis方便的最重要的原因之一,其实就是在解析配置,以及你的mapper.xml文件(此处只聊xml方式,注解方式大可推理出来),将所有的配置项,mapper中的CRUD片段都封装到一个org.apache.ibatis.session.Configuration中,configuration中,其他的就不过多的解释了,重要的是
protected final Map mappedStatements = new StrictMap(
"Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource()
+ " and " + targetValue.getResource());
首先要明确一点—>一个MappedStatement对应一个什么??? 答案是:一个 select|insert|update|delete标签,对应一个MappedStatement
在这个map集合中,key–>namespace+crud标签的id,,所以也就解释了为什么同一个mapper中的方法是真的不能重载!!!!
value的MappedStatement中就包括了:parameterMap,resultMaps以及一级缓存二级缓存的开启情况,当然sql的情况封装在了SqlSource中,SqlSource,sql源,到这里,大家应该都还可以比较简单的理解到,但是,很多同事就是读到这里读不下去了:
为什么呢?SqlSource中封装了一个sqlNode,而一个普通的sqlNode对应又是什么?
比如:
SELECT * FROM user WHERE id = #{id}
AND username like '%${username}'
AND 1=1
第一个sqlNode
SELECT * FROM user WHERE id = #{id}
第二个sqlNode
AND username like '%${username}'
AND 1=1
注意:此处还没有完
第二个中的第一个sqlNode
AND username like '%${username}'
第二个中的第二个sqlNode
AND 1=1
说的直白点,sqlSource中的sqlNode是一个可以做树状结构的sqlnode,很多人就一直不明白为什么一个sqlnode在执行阶段就变成了一堆,所以就读不下去了,因为MixedSqlNode 的出现,它虽然实现了SqlNode接口,但是他的构造中,必须要传一个sqlNode的集合过来,注意一下这个位置,就很好搞清楚了
public class MixedSqlNode implements SqlNode {
private final List contents;
public MixedSqlNode(List contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}
像是其他的就比较简单:
IfSqlNode 见词生意,if片段对应的sqlnode,其中有一个test的OGNL表达式也就是mapper中的判断语句
StaticSqlNode,其实就是只包含? 或者?都不包含的sqlnode 了,已经可以执行第一个预编译的sqlnode封装
TextSqlNode,简单文本sql,可能会包含${}、#{}需要处理。
封装configuration重要的地方大概就这么多,下面聊一聊执行阶段!!!
根据上面得到的sqlSessionfactory,调用其openSession方法,得到一个sqlSession,而执行又需要什么? 上面提到一个集合mappedStatements ,想得到其中的一个value,都是需要得到mappedStatements 的一个key,也就是namespace+id,通用的写法,nameSpace就是mapper.java的全路径,id就是mapper中的方法名,所以无论你是通过反射,或是别的途径,都需要得到这两个值,来得到我们sql封装的mapperedStatement;
然后调用sqlSession的selectOne/selectList进入执行,那么新的问题又会出现,用什么来执行?---->Executor
这里又是mybatis玩的比较花的地方,他的执行器并不是一个执行器,或许这句话有点绕,
比如如果你开启了二级缓存,根绝你的各种配置,在这个sqlsession创建时,会实例化一个CachingExecutor(也就是二级缓存的执行器),但是在这个CachingExecutor中还有一个执行器delegate,它是什么意思呢?
他的意思就是,如果我二级缓存只做我的事情,我只去二级缓存里面找,找不到,不好意思,我就不干了,
扔到下面的delegate去执行吧,面向对象有没有????? 类的单一职责有没有????
而在BaseExecutor 也就是默认开启的一级缓存中,大概也是这么个意思,只是他得小弟执行器是wrapper,见到这个词大家也应该明白这里用了什么模式吧,就不多赘述了,下面就直接聊,我们真正干活的人----->SimpleExecutor(default、simple这些一听也就知道是真正做事的人)
protected Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
stmt.closeOnCompletion();
return handler.queryCursor(stmt);
}
对于上面的一些handler,其实就是一些处理器,谁的事情谁自己去处理,还是类单一职责;
StatementHandler—就是将RawSqlSource、DnamicSqlSource都可以转成StaticSqlSource
他们的关键在于,
1:先将${}直接替换为参数集合中的值
2:将#{}替换为?(此处如果是复杂类型的话,会将#{}中的那个值放入到一个list中)
3:然后进行预处理,当然这里说的是prepared类型
4:循环上面的list,为什么???? 因为jdbc预编译之后的setObjct(位置,值),这个位置,就是1,2,3,4真的没有比list的索引+1 更合适的了!!!,而值怎么取??? list中不仅有索引哦,还有值,也就是#{id}中的id,去入参数集合中得到就可以了!!!