mybatis源码阅读简短体会

mybatis源码阅读简短体会

Mybatis–市场占有率非常高的持久层框架

好!我是Whim,帝都的一个小小java,这篇博客主要是笼统的记录一下mybatis的主流程。当然,这里并不是,要上多少源码,一步一步解析清楚,而是简短的介绍一个,主要流程以及映射关系

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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
			"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}
        <if test="username != null and username !='' ">
            AND username like '%${username}'
            <if test="username != null and username !=''">
                AND 1=1
            </if>
        </if>
第一个sqlNode
SELECT * FROM user WHERE id = #{id}  
第二个sqlNode
<if test="username != null and username !='' ">
           AND username like '%${username}'
           <if test="username != null and username !=''">
               AND 1=1
           </if>
</if>   

注意:此处还没有完

第二个中的第一个sqlNode
AND username like '%${username}'
第二个中的第二个sqlNode
<if test="username != null and username !=''">
             AND 1=1
         </if>

说的直白点,sqlSource中的sqlNode是一个可以做树状结构的sqlnode,很多人就一直不明白为什么一个sqlnode在执行阶段就变成了一堆,所以就读不下去了,因为MixedSqlNode 的出现,它虽然实现了SqlNode接口,但是他的构造中,必须要传一个sqlNode的集合过来,注意一下这个位置,就很好搞清楚了

public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;

public MixedSqlNode(List<SqlNode> 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重要的地方大概就这么多,下面聊一聊执行阶段!!!

执行(根据select)

根据上面得到的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 <E> Cursor<E> 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,去入参数集合中得到就可以了!!!

Whim7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值