mybatis源码分析(方法调用过程)

十一月月底,宿舍楼失火啦,搞得20多天没有网,目测直到放假也不会来了。。。

 

正题

嗯~,其实阅读源码不是为了应付面试,更重要的让你知道,大师是怎样去写代码的,同样是用Java,为啥Clinton Begin写的叫源码,而你写只能叫代码。

最简单的入门代码:

先读取配置文件流,然后构造个SqlSessionFactory,然后开启一个SqlSession,指定statement,调用查询方法,返回结果。那么,你知道他是怎样实现的吗

 SqlSessionFactoryBuilder.build 方法

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);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 的构造方法来创建一个 XMLConfigBuilder 对象, 调用 XMLConfigBuilder parse 方法进行解析配置文件,返回一个 Configuration 对象

  public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

 

将返回的 Configuration 对象传入另外一个重载的 build 方法,实际上是传入了 DefaultSqlSessionFactory 的构造方法,返回 sqlSessionFactory

我比较关心的是 XMLConfigBuilder parse 方法,都干了什么事情

首先进入 XMLConfigBuilder 的构造方法后, 真正使用配置文件输入流的是 XPathParser, 它是负责解析 XML文件元素节点的, 通俗地讲, XpathParser 负责将原料加工成零件, XMLConfigBuilder 负责按照工序组装零件成一个产品。

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}

 

经过构造方法初始化好XPathParser后,就要进入parse方法了。Parse 方法里有个判断,如果已经解析过了,就会抛出异常,如果没解析,就将解析标志设为 true。接着调用parseConfiguration 

  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}private void parseConfiguration(XNode root) {try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));loadCustomVfs(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectionFactoryElement(root.evalNode("reflectionFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

 

parseConfiguration 就是工序图, 组装产品一定是按照一定顺序的, 所以这也是建造者模式的核心。
比如:第一个是全局设置 settings,第二个是属性文件,第三个是别名。在这里我们看 environmentsElement
很明显它是来构建 Environment 的, 也就是我们配置的数据源信息。

  private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));    // 构建事务工厂DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));    // 构建数据源工厂DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());}}}}

主要有两大块: transactionManagerElement dataSourceElement

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties props = context.getChildrenAsProperties();TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a TransactionFactory.");}private DataSourceFactory dataSourceElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties props = context.getChildrenAsProperties();DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a DataSourceFactory.");}

注意看: resolveClass(type).newInstance(), 这不就是反射吗?

而那个 type 则是我们配置文件里面配置的,比如 jdbc 或是 manage, 对应 JdbcTransactionFactory ManagedTransactionFactory

Upooled Pooled对应 UnpooledDataSourceFactory PooledDataSourceFactory

返回 environmentsElement 方法, 我们还看到 Environment 有个 Builder 类, 准确来说是静态内部类。

          Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());

 

具体的Environment类:

public final class Environment {private final String id;private final TransactionFactory transactionFactory;private final DataSource dataSource;public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {if (id == null) {throw new IllegalArgumentException("Parameter 'id' must not be null");}if (transactionFactory == null) {throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");}this.id = id;if (dataSource == null) {throw new IllegalArgumentException("Parameter 'dataSource' must not be null");}this.transactionFactory = transactionFactory;this.dataSource = dataSource;}public static class Builder {private String id;private TransactionFactory transactionFactory;private DataSource dataSource;public Builder(String id) {this.id = id;}public Builder transactionFactory(TransactionFactory transactionFactory) {this.transactionFactory = transactionFactory;return this;}public Builder dataSource(DataSource dataSource) {this.dataSource = dataSource;return this;}public String id() {return this.id;}public Environment build() {return new Environment(this.id, this.transactionFactory, this.dataSource);}}public String getId() {return this.id;}public TransactionFactory getTransactionFactory() {return this.transactionFactory;}public DataSource getDataSource() {return this.dataSource;}}

 

那么这里有一个设计模式的问题。为什么Environment里面要搞一个Builder类呢?直接使用构造方法不也可以达到相同的目的吗?

1. 首先, 用内部类是因为内部类与外部类有一定的关系, 往往只有该外部类调用此内部类。 静态内部类
只能访问静态的成员变量和方法,不能访问非静态变量的方法。但是普通内部类可以访问任意外部类
的成员变量和方法。静态内部类可以声明普通成员变量和方法,但是普通内部类不能声明 static 变量
或方法。
静态内部类: Inner I = new Outer.Inner();
普通内部类: Outer o = new Outer(); Inner I = o.new Inner();
2. 另外, 静态都是用来修饰类的内部成员的, 比如静态方法, 静态成员变量。 静态方法不能访问非静态
变量和非静态方法。 Static 不能修饰局部变量。
3. 总结:如果类的构造函数有多个参数,设计这样的类时, 最好使用 Builder 模式, 特别是大多数参数
都是可选的时候。如果现在不能确定参数的个数,最好一开始就使用建造者模式。 

 到此,SqlSessionFactoryBuilder.build方法的作用是:解析配置文件,构建唯一的Configuration对象,构建全局唯一并且线程安全的SqlSessionFactory。

 

SqlSessionFactory的openSession方法

 进入openSession方法

  @Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}

 

会发现它调用的是另外一个方法。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

 

看try块第一行,获得的Environment是前面由XMLConfigBuilder装配到Configuration里面的。第二三行的事务工厂和数据源都是在解析配置文件期间装配到Environment里面的。

到第四行,这是个新东西Executor,简单来说它是真正执行CRUD操作的工具,给我们提供的SqlSession仅仅是个用户接口。Executor也是由Configuration对象来创建的,可见Configuration是多么重要。

我们知道事务操作必不可少,所以Executor的创建必须有Transaction对象。

第五行,就是创建SqlSession了,DefaultSqlSession是一个具体实现类。我们可以看到它把Executor传进去了,那么就不难发现,SqlSession不过是件衣裳。

接下来看SqlSession调用过程

 

调用 SqlSession selectList 方法

先看selectOne方法

  @Overridepublic <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.<T>selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}

 

看到这句话没

this.<T>selectList(statement, parameter);

我们调用返回一条数据的方法,实际上也是调用selectList,现在看selectList:多个重载方法我就不全部贴了

  @Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

 

MappedStatement你可以理解为你配置文件里面写的sql语句的映射,比如:

同样,这个对象也来自Configuration。接着看,return的是Executor的query方法,ms作为参数。Executor有多个实现类,BaseExecutor是最基础的实现,来看其中的query实现:

  @Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}

 

第一行,从ms里面获得绑定的sql语句,脑袋是不是跟配置产生一点联系了?第二行不需要管,看第三行的实现:

  @SuppressWarnings("unchecked")@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601
      deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482
        clearLocalCache();}}return list;}

 

当查询的时候先从缓存中找,如果找不到就从数据库中找,这里我们看

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

 

 这句是关键:

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

在BaseExecutor里面有定义

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;

 

它是需要子类去实现。总共有三种:BatchExecutor,ReuseExecutor,SimpleExecutor。这里我们选择SimpleExecutor:

  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

 

 这里说一句题外话:到处都需要Configuration,它无疑是mybatis运行期间的核心。

StatementHandler是对java.sql.Statement的封装处理,有三个实现类:CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler。

同样,利用Configuration来创建一个StatementHandler实例,之后利用这个handler来创建一个java.sql.statement,最后调用handler的<E>query方法,利用原生的Statement来执行查询操作。

PreparedStatementHandler.query方法

  @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.<E> handleResultSets(ps);}

 

 前两行都应该知道,类型转换以及执行jdbc的查询。最后一行是利用原生的PreparedStatement来进行结果集的封装。ResultSetHandler有个默认实现类:DefaultResultSetHandler,具体就不在分析了。

到此为止,返回结果集到最上层,显示给用户。

 

先写这些吧,写的不是特别满意,望指教~

 

转载于:https://www.cnblogs.com/LUA123/p/8094573.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/539756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

提取多个字段_动态合并多个工作表,数据再多也不怕

小伙伴们好啊&#xff0c;今天老祝和大家分享一个动态合并多个工作表的技巧。很多时候&#xff0c;咱们的数据是按照部门或是月份等项目&#xff0c;分别存放在不同工作表中的&#xff0c;要对这些数据进行分析汇总的时候&#xff0c;需要先将不同工作表中的数据合并到一起才可…

深入理解Oracle的并行操作【好文认真读】

请尊重原文作者&#xff0c;http://czmmiao.iteye.com/blog/1487568 -------------------------------------------------------------------------------- 并行&#xff08;Parallel&#xff09;和OLAP系统 并行的实现机制是&#xff1a;首先&#xff0c;Oracle会创建一个进…

服务器虚拟化性能瓶颈怎么办,如何突破虚拟化三大瓶颈

如果你希望在应用虚拟化技术的过程中不出现任何问题的话&#xff0c;那么显然这是不切合实际的期望。虚拟化技术能给你的数据中心带来诸多好处&#xff0c;但是为了可以利用虚拟化的优势&#xff0c;你需要了解可能会面对哪些问题。即使虚拟化技术已经被应用到了许多企业中&…

2017-2018-1 20155229 《信息安全系统设计基础》第十四周学习总结

2017-2018-1 20155229 《信息安全系统设计基础》第十四周学习总结 对“第三章 程序机器级表示”的深入学习 我选择这章的理由是第一次学的时候还是不太理解&#xff0c;老师也有说这章建议在认真学习&#xff0c;所以本周的学习任务是认真再次学习这一章c语言、汇编代码以及机器…

python中为什么推荐使用with_Python中的with关键字使用详解

这篇文章主要介绍了Python 中的with关键字使用详解的相关资料,在Python中,with关键字是一个替你管理实现上下文协议对象的好东西,需要的朋友可以参考下">在 Python 2.5 中&#xff0c; with 关键字被加入。它将常用的 try ... except ... finally ... 模式很方便的被复…

create table as select性能测试

转载自&#xff1a;http://blog.csdn.net/yangzhijun_cau/article/details/7396088 --------------------------------------------------------------------------------- 原表270W数据&#xff0c;无照片&#xff0c;字段比较多&#xff0c;有50个左右 测试机是一个虚拟机&a…

类似索引Model套Model之 iOS模型闲聊二

看下界面, 这是类似于索引的页面, 只不过木有右侧索引条的布局. 如果想了解通讯录索引的,请移步iOS - 高仿通讯录之商品索引排序搜索. 提供思路如下: 分析界面及接口用 MVC 设计模式来实现(其实核心点都在下面5)创建内外层 Model 并绑定两者 Model两者 Cell 布局的实现 (便于后…

输入法画面_搜狗输入法:用AI技术谱写诗意生活

十九世纪著名的思想家斯宾塞曾说&#xff1a;科学本身就富有诗意。这里应该包含两种意思&#xff0c;字面上&#xff0c;科学是饱含文字之美的&#xff0c;比如原理和规律的推演&#xff0c;仅通过文字符号的简单排列&#xff0c;便有了生机。但深层次上科学又不止于文字&#…

hadoop伪分布式(单机版)安装,Linux

一、下载 1、hadoop官网下载&#xff1a;https://archive.apache.org/dist/hadoop/common/ 进入stable文件夹里下载&#xff0c;这是稳定版本。 stable/ 本文的版本是 hadoop-2.7.2.tar.gz 2、jdk下载&#xff0c;JDK7及以上&#xff0c;本文用jdk8-64位 二、版本区别 2.…

c++-add two numbers 两个链表相加

题目描述 You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. Input: (2 -> 4 -> 3) (5 -> 6…

城轨的两类时钟系统均同步于_基于两台SDS3000示波器同步产生“8通道”示波器...

在很多应用场合需要4通道以上的示波器&#xff0c;但是市面上极大部分示波器最多只有四通道&#xff0c;而且没有外部输入的同步时钟接口。 有什么快捷的方法获得更多通道功能的示波器&#xff1f; 最简便的方法是:将两台示波器的辅助输入信号作为触发源&#xff0c;同时连接到…

Linux设置ssh免密码登录

一、SSH来源 对于需要远程管理其它机器&#xff0c;一般使用远程桌面或者telnet。linux一般只能是telnet。但是telnet的缺点是通信不加密&#xff0c;存在不安全因素&#xff0c;只适合内网访问。 为解决这个问题&#xff0c;推出了通信加密通信协议&#xff0c;即SSH&#x…

解析json数据_Retrofit同时解析JSON和XML数据格式

前言Android开发中&#xff0c;我们会经常遇到前端需要解析两种数据格式(json和xml),比如自己服务器返回的是json格式的数据&#xff0c;我们做微信登录的时候&#xff0c;微信返回的格式又是xml格式的。我们可以通过自己编写Retrofit的ConverterFactory来做到可以同时解析两种…

ORACLE 小时值必须介于1和12之间 解决方法

ORACLE数据库查询语句&#xff1a; "select * from dual where time>to_date(2012-10-29 19:45:34,yyyy-mm-dd HH:mi:ss)"当执行时&#xff0c;会抛出错误&#xff1a;ORA-01849: 小时值必须介于 1 和 12 之间 01849. 00000 - "hour must be between 1 and 1…

jenkins+svn+maven+ssh 部署配置详细记录

2019独角兽企业重金招聘Python工程师标准>>> 先简单记录一下&#xff0c;后面再慢慢完善。 1、环境 jdk 1.7.0_45 maven 3.1.1 jenkins 2.3.21 jdk和maven的安装就不必多说了&#xff0c;主要是jenkins的安装需要说下&#xff0c;jenkins有war包和yum还有rpm等安装方…

k8s安装sqlite3_kubernetes环境部署单节点redis数据库的方法

kubernetes部署redis数据库(单节点)redis简介Redis 是我们常用的非关系型数据库&#xff0c;在项目开发、测试、部署到生成环境时&#xff0c;经常需要部署一套 Redis 来对数据进行缓存。这里介绍下如何在 Kubernetes 环境中部署用于开发、测试的环境的 Redis 数据库&#xff0…

oracle 都是parallel惹的祸【1-2分钟出结果变1-2秒】

原文&#xff1a;http://blog.csdn.net/shushugood/article/details/9000628 -------------------------------------------------------- 该项目是中国联通xxxx话务系统&#xff0c;我的架构设计需求设计&#xff0c;运维保障数据库开发&#xff0c;全套服务。 在今天开发完毕…

二叉搜索树(BST树)的简单实现

#include <stdlib.h>template<typename T>class CBinSTree;template <typename T>class CTreeNode{//树节点类public:CTreeNode(const T& item,CTreeNode<T>* lptr NULL,CTreeNode<T>* rptr NULL):data(item),left(lptr),right(rptr){}CTr…

Oracle 创建 DBLink 的方法

原文出处&#xff1a;http://blog.csdn.net/davidhsing/article/details/6408770 ------------------- 1、如果需要创建全局 DBLink&#xff0c;则需要先确定用户有创建 dblink 的权限&#xff1a; [c-sharp] view plaincopy print?select * from user_sys_privs where privi…

eclipse init 配置

--设置最大的堆和最小堆大小.两者一样表示固定大小.这样可以防止老年代内存扩展造成额外的gc.当然也会多占一些内存.系统内存不足的慎用 -Xms512m -Xmx512m --加大年轻代内存.减少minor gc -Xmn164m --这个是永久代大小.默认是64M,增加到96M.固定大小,减少扩展造成的gc -XX:Per…