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.tpcf.cn/news/539756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…