MyBatis 是第一个支持自定义 SQL、存储过程和高级映射的类持久框架。MyBatis 消除了大部分 JDBC 的样板代码、手动设置参数以及检索结果。MyBatis 可以支持简单的 XML 和注解配置规则。使 Map 接口和 POJO 类映射到数据库字段和记录。html
那么 MyBatis 具备什么特色呢?或许咱们能够从以下几个方面来描述java
自己就很小且简单。没有任何第三方依赖,只要经过配置 jar 包,或者若是你使用 Maven 项目的话只须要配置 Maven 以来就能够。易于使用,经过文档和源代码,能够比较彻底的掌握它的设计思路和实现。mysql
MyBatis 回屏蔽原始的 JDBC 样板代码,让你把更多的精力专一于 SQL 的书写和属性-字段映射上。sql
MyBatis 最主要的特色就是你能够手动编写 SQL 语句,可以支持多表关联查询。数据库
ORM 是什么?
对象关系映射(Object Relational Mapping,简称ORM)
,是经过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另一种形式。
你可使用 MyBatis XML 标签,起到 SQL 模版的效果,减小繁杂的 SQL 语句,便于维护。apache
MyBatis 最上面是接口层,接口层就是开发人员在 Mapper 或者是 Dao 接口中的接口定义,是查询、新增、更新仍是删除操做;中间层是数据处理层,主要是配置 Mapper -> XML 层级之间的参数映射,SQL 解析,SQL 执行,结果映射的过程。上述两种流程都由基础支持层来提供功能支撑,基础支持层包括链接管理,事务管理,配置加载,缓存处理等。设计模式
在不与Spring 集成的状况下,使用 MyBatis 执行数据库的操做主要以下:缓存
InputStream is = Resources.getResourceAsStream("myBatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); sqlSession = factory.openSession();
其中的SqlSessionFactory
,SqlSession
是 MyBatis 接口的核心类,尤为是 SqlSession,这个接口是MyBatis 中最重要的接口,这个接口可以让你执行命令,获取映射,管理事务。服务器
在 Mybatis 初始化过程当中,会加载 mybatis-config.xml
配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会造成相应的对象并保存到 Configration
对象中。以后,根据该对象建立SqlSessionFactory 对象。待 Mybatis 初始化完成后,能够经过 SqlSessionFactory 建立 SqlSession 对象并开始数据库操做。session
Mybatis 实现的动态 SQL 语句,几乎能够编写出全部知足须要的 SQL。
Mybatis 中 scripting 模块会根据用户传入的参数,解析映射文件中定义的动态 SQL 节点,造成数据库能执行的SQL 语句。
SQL 语句的执行涉及多个组件,包括 MyBatis 的四大核心,它们是: Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
。SQL 的执行过程能够用下面这幅图来表示
MyBatis 层级结构各个组件的介绍(这里只是简单介绍,具体介绍在后面):
SqlSession
: ,它是 MyBatis 核心 API,主要用来执行命令,获取映射,管理事务。接收开发人员提供 Statement Id 和参数。并返回操做结果。Executor
:执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的维护。StatementHandler
: 封装了JDBC Statement 操做,负责对 JDBC Statement 的操做,如设置参数、将Statement 结果集转换成 List 集合。ParameterHandler
: 负责对用户传递的参数转换成 JDBC Statement 所须要的参数。ResultSetHandler
: 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。TypeHandler
: 用于 Java 类型和 JDBC 类型之间的转换。MappedStatement
: 动态 SQL 的封装SqlSource
: 表示从 XML 文件或注释读取的映射语句的内容,它建立将从用户接收的输入参数传递给数据库的 SQL。Configuration
: MyBatis 全部的配置信息都维持在 Configuration 对象之中。Mybatis 中的反射模块,对 Java 反射进行了很好的封装,提供了简易的 API,方便上层调用,而且对反射操做进行了一系列的优化,好比,缓存了类的 元数据(MetaClass)
和对象的元数据(MetaObject)
,提升了反射操做的性能。
Mybatis 的别名机制,可以简化配置文件,该机制是类型转换模块的主要功能之一。类型转换模块的另外一个功能是实现 JDBC 类型与 Java 类型的转换。在 SQL 语句绑定参数时,会将数据由 Java 类型转换成 JDBC 类型;在映射结果集时,会将数据由 JDBC 类型转换成 Java 类型。
在 Java 中,有不少优秀的日志框架,如 Log4j、Log4j二、slf4j 等。Mybatis 除了提供了详细的日志输出信息,还可以集成多种日志框架,其日志模块的主要功能就是集成第三方日志框架。
该模块主要封装了类加载器,肯定了类加载器的使用顺序,并提供了加载类文件和其它资源文件的功能。
该模块有两个主要功能:一个是封装了 XPath
,为 Mybatis 初始化时解析 mybatis-config.xml
配置文件以及映射配置文件提供支持;另外一个为处理动态 SQL 语句中的占位符提供支持。
Mybatis 自身提供了相应的数据源实现,也提供了与第三方数据源集成的接口。数据源是开发中的经常使用组件之一,不少开源的数据源都提供了丰富的功能,如链接池、检测链接状态等,选择性能优秀的数据源组件,对于提供ORM 框架以及整个应用的性能都是很是重要的。
通常地,Mybatis 与 Spring 框架集成,由 Spring 框架管理事务。但 Mybatis 自身对数据库事务进行了抽象,提供了相应的事务接口和简单实现。
Mybatis 中有一级缓存
和二级缓存
,这两级缓存都依赖于缓存模块中的实现。可是须要注意,这两级缓存与Mybatis 以及整个应用是运行在同一个 JVM 中的,共享同一块内存,若是这两级缓存中的数据量较大,则可能影响系统中其它功能,因此须要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。
在调用 SqlSession
相应方法执行数据库操做时,须要制定映射文件中定义的 SQL 节点,若是 SQL 中出现了拼写错误,那就只能在运行时才能发现。为了能尽早发现这种错误,Mybatis 经过 Binding 模块将用户自定义的Mapper 接口与映射文件关联起来,系统能够经过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操做,从而避免上述问题。注意,在开发中,咱们只是建立了 Mapper 接口,而并无编写实现类,这是由于 Mybatis 自动为 Mapper 接口建立了动态代理对象。
在认识了 MyBatis 并了解其基础架构以后,下面咱们来看一下 MyBatis 的核心组件,就是这些组件实现了从 SQL 语句到映射到 JDBC 再到数据库字段之间的转换,执行 SQL 语句并输出结果集。首先来认识 MyBatis 的第一个核心组件
对于任何框架而言,在使用该框架以前都要经历过一系列的初始化流程,MyBatis 也不例外。MyBatis 的初始化流程以下
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSessionFactory.openSession();
上述流程中比较重要的一个对象就是SqlSessionFactory
,SqlSessionFactory 是 MyBatis 框架中的一个接口,它主要负责的是
SqlSession
对象SqlSessionFactory
有两个实现类,一个是 SqlSessionManager 类,一个是 DefaultSqlSessionFactory 类
DefaultSqlSessionFactory
: SqlSessionFactory 的默认实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。下面来对 SqlSessionFactory 的执行流程来作一个分析
首先第一步是 SqlSessionFactory 的建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
从这行代码入手,首先建立了一个 SqlSessionFactoryBuilder
工厂,这是一个建造者模式的设计思想,由 builder 建造者来建立 SqlSessionFactory 工厂
而后调用 SqlSessionFactoryBuilder 中的 build
方法传递一个InputStream
输入流,Inputstream 输入流中就是你传过来的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根据传入的 InputStream 输入流和environment
、properties
属性建立一个XMLConfigBuilder
对象。SqlSessionFactoryBuilder 对象调用XMLConfigBuilder 的parse()
方法,流程以下。
XMLConfigBuilder 会解析/configuration
标签,configuration 是 MyBatis 中最重要的一个标签,下面流程会介绍 Configuration 标签。
MyBatis 默认使用 XPath 来解析标签,关于 XPath 的使用,参见 https://www.w3school.com.cn/x...
在 parseConfiguration
方法中,会对各个在 /configuration
中的标签进行解析
说一下这些标签都是什么意思吧
properties
,外部属性,这些属性都是可外部配置且可动态替换的,既能够在典型的 Java 属性文件中配置,亦可经过 properties 元素的子元素来传递。<properties> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="root" /> </properties>
通常用来给 environment
标签中的 dataSource
赋值
<environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment>
还能够经过外部属性进行配置,可是咱们这篇文章以原理为主,不会介绍太多应用层面的操做。
settings
,MyBatis 中极其重要的配置,它们会改变 MyBatis 的运行时行为。settings 中配置有不少,具体能够参考 https://mybatis.org/mybatis-3... 详细了解。这里介绍几个日常使用过程当中比较重要的配置
属性 | 描述 |
---|---|
cacheEnabled | 全局地开启或关闭配置文件中的全部映射器已经配置的任何缓存。 |
useGeneratedKeys | 容许 JDBC 支持自动生成主键,须要驱动支持。 若是设置为 true 则这个设置强制使用自动生成主键。 |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,全部关联对象都会延迟加载。 |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动须要指定列的 JDBC 类型,多数状况直接用通常类型便可,好比 NULL、VARCHAR 或 OTHER。 |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种状况下会缓存一个会话中执行的全部查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不一样调用将不会共享数据 |
proxyFactory | 指定 Mybatis 建立具备延迟加载能力的对象所用到的代理工具。 |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的相似映射。 |
通常使用以下配置
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings>
typeAliases
,类型别名,类型别名是为 Java 类型设置的一个名字。 它只和 XML 配置有关。<typeAliases> <typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases>
当这样配置时,Blog
能够用在任何使用 domain.blog.Blog
的地方。
typeHandlers
,类型处理器,不管是 MyBatis 在预处理语句(PreparedStatement)
中设置一个参数时,仍是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。在 org.apache.ibatis.type
包下有不少已经实现好的 TypeHandler,能够参考以下
你能够重写类型处理器或建立你本身的类型处理器来处理不支持的或非标准的类型。
具体作法为:实现 org.apache.ibatis.type.TypeHandler
接口, 或继承一个很方便的类 org.apache.ibatis.type.BaseTypeHandler
, 而后能够选择性地将它映射到一个 JDBC 类型。
objectFactory
,对象工厂,MyBatis 每次建立结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂须要作的仅仅是实例化目标类,要么经过默认构造方法,要么在参数映射存在的时候经过参数构造方法来实例化。若是想覆盖对象工厂的默认行为,则能够经过建立本身的对象工厂来实现。public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public <T> boolean isCollection(Class<T> type) { return Collection.class.isAssignableFrom(type); } }
而后须要在 XML 中配置此对象工厂
<objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory>
plugins
,插件开发,插件开发是 MyBatis 设计人员给开发人员留给自行开发的接口,MyBatis 容许你在已映射语句执行过程当中的某一点进行拦截调用。MyBatis 容许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,这几个接口也是 MyBatis 中很是重要的接口,咱们下面会详细介绍这几个接口。environments
,MyBatis 环境配置,MyBatis 能够配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境须要有不一样的配置;或者想在具备相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。
这里注意一点,虽然 environments 能够指定多个环境,可是 SqlSessionFactory 只能有一个,为了指定建立哪一种环境,只要将它做为可选的参数传递给 SqlSessionFactoryBuilder 便可。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
环境配置以下
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
databaseIdProvider
,数据库厂商标示,MyBatis 能够根据不一样的数据库厂商执行不一样的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性。
<databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> </databaseIdProvider>
mappers
,映射器,这是告诉 MyBatis 去哪里找到这些 SQL 语句,mappers 映射配置有四种方式
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 使用彻底限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的彻底限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 将包内的映射器接口实现所有注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
上面的一个个属性都对应着一个解析方法,都是使用 XPath 把标签进行解析,解析完成后返回一个 DefaultSqlSessionFactory
对象,它是 SqlSessionFactory 的默认实现类。这就是 SqlSessionFactoryBuilder 的初始化流程,经过流程咱们能够看到,初始化流程就是对一个个 /configuration
标签下子标签的解析过程。
在 MyBatis 初始化流程结束,也就是 SqlSessionFactoryBuilder -> SqlSessionFactory 的获取流程后,咱们就能够经过 SqlSessionFactory 对象获得 SqlSession
而后执行 SQL 语句了。具体来看一下这个过程
在 SqlSessionFactory.openSession 过程当中咱们能够看到,会调用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource
方法,这个方法主要建立了两个与咱们分析执行流程重要的对象,一个是 Executor
执行器对象,一个是 SqlSession
对象。执行器咱们下面会说,如今来讲一下 SqlSession 对象
SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口可以让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你可以执行简单的 CRUD
操做,也能够经过 getMapper
获取 Mapper 层,执行自定义 SQL 语句,由于 SqlSession 在执行 SQL 语句以前是须要先开启一个会话,涉及到事务操做,因此还会有 commit
、 rollback
、close
等方法。这也是模版设计模式的一种应用。
MapperProxy 是 Mapper 映射 SQL 语句的关键对象,咱们写的 Dao 层或者 Mapper 层都是经过 MapperProxy
来和对应的 SQL 语句进行绑定的。下面咱们就来解释一下绑定过程
这就是 MyBatis 的核心绑定流程,咱们能够看到 SqlSession 首先调用 getMapper
方法,咱们刚才说到 SqlSession 是大哥级别的人物,只定义标准(有一句话是怎么说的来着,一流的企业作标准,二流的企业作品牌,三流的企业作产品)。
SqlSession 不肯意作的事情交给 Configuration
这个手下去作,可是 Configuration 也是有小弟的,它不肯意作的事情直接甩给小弟去作,这个小弟是谁呢?它就是 MapperRegistry
,立刻就到核心部分了。MapperRegistry 至关于项目经理,项目经理只从大面上把握项目进度,不须要知道手下的小弟是如何工做的,把任务完成了就好。最终真正干活的仍是 MapperProxyFactory
。看到这段代码 Proxy.newProxyInstance ,你是否是有一种恍然大悟的感受,若是你没有的话,建议查阅一下动态代理的文章,这里推荐一篇 (https://www.jianshu.com/p/959...)
也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是经过动态代理来完成的。
经过动态代理,咱们就能够方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操做了。那么具体的执行过程是怎么样呢?上面只是绑定过程,别着急,下面就来探讨一下 SQL 语句的执行过程。
有一部分代码被遮挡,代码有些多,不过不影响咱们看主要流程
MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute
方法比较长,其实逻辑比较简单,就是判断是 插入
、更新
、删除
仍是 查询
语句,其中若是是查询的话,还会判断返回值的类型,咱们能够点进去看一下都是怎么设计的。
不少代码其实能够忽略,只看我标出来的重点就行了,咱们能够看到,无论你前面通过多少道关卡处理,最终都逃不过 SqlSession
这个老大制定的标准。
咱们以 selectList
为例,来看一下下面的执行过程。
这是 DefaultSqlSession
中 selectList 的代码,咱们能够看到出现了 executor
,这是什么呢?咱们下面来解释。
还记得咱们以前的流程中提到了 Executor(执行器)
这个概念吗?咱们来回顾一下它第一次出现的位置。
由 Configuration 对象建立了一个 Executor
对象,这个 Executor 是干吗的呢?下面咱们就来认识一下
每个 SqlSession 都会拥有一个 Executor 对象,这个对象负责增删改查的具体操做,咱们能够简单的将它理解为 JDBC 中 Statement 的封装版。 也能够理解为 SQL 的执行引擎,要干活总得有一个发起人吧,能够把 Executor 理解为发起人的角色。
首先先从 Executor 的继承体系来认识一下
如上图所示,位于继承体系最顶层的是 Executor 执行器,它有两个实现类,分别是BaseExecutor
和 CachingExecutor
。
BaseExecutor
是一个抽象类,这种经过抽象的实现接口的方式是适配器设计模式之接口适配
的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,下降了接口实现的难度。BaseExecutor 的子类有三个,分别是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。
SimpleExecutor
: 简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(能够是 Statement 或者是 PreparedStatment 对象)
ReuseExecutor
: 可重用执行器,这里的重用指的是重复使用 Statement,它会在内部使用一个 Map 把建立的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,若是存在 Statement 对象而且对应的 connection 尚未关闭的状况下就继续使用以前的 Statement 对象,并将其缓存起来。由于每个 SqlSession 都有一个新的 Executor 对象,因此咱们缓存在 ReuseExecutor 上的 Statement做用域是同一个 SqlSession。
BatchExecutor
: 批处理执行器,用于将多个 SQL 一次性输出到数据库
CachingExecutor
: 缓存执行器,先从缓存中查询结果,若是存在就返回以前的结果;若是不存在,再委托给Executor delegate 去数据库中取,delegate 能够是上面任何一个执行器。
咱们上面提到 Executor
是由 Configuration 建立的,Configuration 会根据执行器的类型建立,以下
这一步就是执行器的建立过程,根据传入的 ExecutorType
类型来判断是哪一种执行器,若是不指定 ExecutorType ,默认建立的是简单执行器。它的赋值能够经过两个地方进行赋值:
<settings>
标签来设置当前工程中全部的 SqlSession 对象使用默认的 Executor<settings> <!--取值范围 SIMPLE, REUSE, BATCH --> <setting name="defaultExecutorType" value="SIMPLE"/> </settings>
session = factory.openSession(ExecutorType.BATCH);
Executor 中的大部分方法的调用链实际上是差很少的,下面是深刻源码分析执行过程,若是你没有时间或者暂时不想深刻研究的话,给你下面的执行流程图做为参考。
咱们紧跟着上面的 selectList
继续分析,它会调用到 executor.query
方法。
当有一个查询请求访问的时候,首先会通过 Executor 的实现类 CachingExecutor
,先从缓存中查询 SQL 是不是第一次执行,若是是第一次执行的话,那么就直接执行 SQL 语句,并建立缓存,若是第二次访问相同的 SQL 语句的话,那么就会直接从缓存中提取。
上面这段代码是从 selectList -> 从缓存中 query 的具体过程。可能你看到这里有些以为类都是什么东西,我想鼓励你一下,把握重点,不用每段代码都看,从找到 SQL 的调用链路,其余代码想看的时候在看,看源码就是很容易发蒙,容易烦躁,可是切记一点,把握重点。
上面代码会判断缓存中是否有这条 SQL 语句的执行结果,若是没有的话,就再从新建立 Executor
执行器执行 SQL 语句,注意, list = doQuery
是真正执行 SQL 语句的过程,这个过程当中会建立咱们上面提到的三种执行器,这里咱们使用的是简单执行器。
到这里,执行器所作的工做就完事了,Executor 会把后续的工做交给 StatementHandler
继续执行。下面咱们来认识一下 StatementHandler
StatementHandler
是四大组件中最重要的一个对象,负责操做 Statement 对象与数据库进行交互,在工做时还会使用 ParameterHandler
和 ResultSetHandler
对参数进行映射,对结果进行实体类的绑定,这两个组件咱们后面说。
咱们在搭建原生 JDBC 的时候,会有这样一行代码
Statement stmt = conn.createStatement(); //也可使用PreparedStatement来作
这行代码建立的 Statement 对象或者是 PreparedStatement 对象就是由 StatementHandler 进行管理的。
有没有感受和 Executor
的继承体系很类似呢?最顶级接口是四大组件对象,分别有两个实现类 BaseStatementHandler
和 RoutingStatementHandler
,BaseStatementHandler 有三个实现类, 他们分别是 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler。
RoutingStatementHandler
: RoutingStatementHandler 并无对 Statement 对象进行使用,只是根据StatementType 来建立一个代理,代理的就是对应Handler的三种实现类。在MyBatis工做时,使用的StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。
BaseStatementHandler
: 是 StatementHandler 接口的另外一个实现类,它自己是一个抽象类,用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类
这里注意一下,SimpleStatementHandler 和 PreparedStatementHandler 的区别是 SQL 语句是否包含变量,是否经过外部进行参数传入。SimpleStatementHandler 用于执行没有任何参数传入的 SQL
PreparedStatementHandler 须要对外部传入的变量和参数进行提早参数绑定和赋值。
咱们继续来分析上面 query
的调用链路,StatementHandler 的建立过程以下
MyBatis 会根据 SQL 语句的类型进行对应 StatementHandler 的建立。咱们以预处理 StatementHandler 为例来说解一下
执行器不只掌管着 StatementHandler 的建立,还掌管着建立 Statement 对象,设置参数等,在建立完 PreparedStatement 以后,咱们须要对参数进行处理了。
若是用一副图来表示一下这个执行流程的话我想是这样
这里咱们先暂停一下,来认识一下第三个核心组件 ParameterHandler
ParameterHandler
相比于其余的组件就简单不少了,ParameterHandler 译为参数处理器,负责为 PreparedStatement 的 sql 语句参数动态赋值,这个接口很简单只有两个方法
ParameterHandler 只有一个实现类 DefaultParameterHandler
, 它实现了这两个方法。
上面咱们讨论过了 ParameterHandler
的建立过程,下面咱们继续上面 parameterSize
流程
这就是具体参数的解析过程了,下面咱们来描述一下
public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // parameterMappings 就是对 #{} 或者 ${} 里面参数的封装 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { // 若是是参数化的SQL,便须要循环取出并设置参数的值 for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); // 若是参数类型不是 OUT ,这个类型与 CallableStatementHandler 有关 // 由于存储过程不存在输出参数,因此参数不是输出参数的时候,就须要设置。 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; // 获得 #{} 中的属性名 String propertyName = parameterMapping.getProperty(); // 若是 propertyName 是 Map 中的key if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params // 经过key 来获得 additionalParameter 中的value值 value = boundSql.getAdditionalParameter(propertyName); } // 若是不是 additionalParameters 中的key,并且传入参数是 null, 则value 就是null else if (parameterObject == null) { value = null; } // 若是 typeHandlerRegistry 中已经注册了这个参数的 Class 对象,即它是 Primitive 或者是String 的话 else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { // 不然就是 Map MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 在经过 SqlSource 的parse 方法获得parameterMappings 的具体实现中,咱们会获得parameterMappings 的 typeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); // 获取 typeHandler 的jdbc type JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
下面用一个流程图表示一下 ParameterHandler 的解析过程,以简单执行器为例
咱们在完成 ParameterHandler 对 SQL 参数的预处理后,回到 SimpleExecutor 中的 doQuery
方法
上面又引出来了一个重要的组件那就是 ResultSetHandler,下面咱们来认识一下这个组件
ResultSetHandler 也是一个很是简单的接口
ResultSetHandler 是一个接口,它只有一个默认的实现类,像是 ParameterHandler 同样,它的默认实现类是DefaultResultSetHandler
MyBatis 只有一个默认的实现类就是 DefaultResultSetHandler
,DefaultResultSetHandler 主要负责处理两件事
按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回便可。
public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; // 获取第一个结果集 ResultSetWrapper rsw = getFirstResultSet(stmt); // 获取结果映射 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); // 结果映射的大小 int resultMapCount = resultMaps.size(); // 校验结果映射的数量 validateResultMapsCount(rsw, resultMapCount); // 若是ResultSet 包装器不是null, 而且 resultmap 的数量 > resultSet 的数量的话 // 由于 resultSetCount 第一次确定是0,因此直接判断 ResultSetWrapper 是否为 0 便可 while (rsw != null && resultMapCount > resultSetCount) { // 从 resultMap 中取出 resultSet 数量 ResultMap resultMap = resultMaps.get(resultSetCount); // 处理结果集, 关闭结果集 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } // 从 mappedStatement 取出结果集 String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); }
其中涉及的主要对象有:
ResultSetWrapper
: 结果集的包装器,主要针对结果集进行的一层包装,它的主要属性有
ResultSet
: Java JDBC ResultSet 接口表示数据库查询的结果。 有关查询的文本显示了如何将查询结果做为java.sql.ResultSet 返回。 而后迭代此ResultSet以检查结果。TypeHandlerRegistry
: 类型注册器,TypeHandlerRegistry 在初始化的时候会把全部的 Java类型和类型转换器进行注册。ColumnNames
: 字段的名称,也就是查询操做须要返回的字段名称ClassNames
: 字段的类型名称,也就是 ColumnNames 每一个字段名称的类型JdbcTypes
: JDBC 的类型,也就是 java.sql.Types 类型ResultMap
: 负责处理更复杂的映射关系在 DefaultResultSetHandler 中处理完结果映射,并把上述结构返回给调用的客户端,从而执行完成一条完整的SQL语句。
文章参考:
mybatis基础,mybatis核心配置文件properties元素
https://mybatis.org/mybatis-3...
深刻浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
https://www.jianshu.com/p/196...
http://www.mybatis.org/mybati...
https://www.cnblogs.com/virgo...
https://blog.csdn.net/Roger_C...
https://blog.csdn.net/qq92486...
[mybatis 源码分析(八)ResultSetHandler 详解](