构建 SqlSessionFactory 过程
重要类的说明
SqlSessionFactory
- 描述:MyBatis 核心类之一
- 作用:【最重要】提供创建 MyBatis 的核心接口
SqlSession
创建:
使用 Builder 模式创建,实际中可以通过
SqlSessionFactoryBuilder
去构建通过
org.apache.ibatis.builder.xml.XMLConfigBuilder
解析 XML 配置文件,将读取的参数存入org.apache.ibatis.session.Configuration
类对象中Configuration
采用单例模式,几乎所有的配置内容都存放在这个单例对象中使用
Configuration
对象创建SqlSessionFactory
,SqlSessionFactory
是一个接口,MyBatis 提供了默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
- 需要提供配置文件和相关参数
Configuration
- 描述:存储了 MyBatis 的几乎所有的配置信息
- 作用:在构建
SqlSessionFactory
的过程中起重要作用:- 读入配置文件,包括基础的 XML 和映射器 XML(或注解)
- 初始化基础配置
- 提供单例,为创建
SessionFactory
服务提供配置的参数 - 执行一些重要对象的初始化方法
在读取并解析配置文件后,将其保存在
Configuration
对象中后,会做一些初始化操作:
properties
全局参数typeAliases
别名Plugins
插件objectFactory
对象工厂objectWrapperFactory
对象包装工厂reflectionFactory
反射工厂settings
环境参数environments
数据库环境databaseIdProvider
数据库标识typeHandlers
类型转换器Mappers
映射器
Mappers
映射器 是最重要的内容,在插件中需要频繁的访问,也是 MyBatis 底层运行原理的基础
※ 构建映射器的内部组成
当 XMLConfigBuilder 解析 XML 的时候,会将每一个 SQL 与其配置的内容保存起来。在 MyBatis 中一条 SQL 和相关的配置信息由三个部分组成:MapperStatement
、SqlSource
、BoundSql
MapperStatement
保存一个映射器节点(select | insert | …)的内容。是一个类,包括了很多配置的 SQL、SQL 的 id、缓存信息、resultMap、parameterType、resultType、resultMap 等重要配置信息。
有一个重要的属性:
sqlSource
(MyBatis 可以通过它来获取 SQL 配置的所有信息)SqlSource
它是一个接口,是
MapperStatement
的一个属性,其实现类有:DynamicSqlSource
、ProviderSqlSource
、RawSqlSource
、StaticSqlSource
。作用:根据上下文和参数解析生成需要的 SQL,接口只定义了一个接口方法 ——
getBoundSql(parameterObject)
,用于提供BoundSql
对象。BoundSql
是一个结果对象,是建立 SQL 和参数的地方,有三个常用的属性:
sql
、parameterObject
、parameterMappings
最终的参数和 SQL 都反映在 BoundSql
类对象上,在插件中往往拿到它就可以获得当前运行的 SQL 和参数,从而修改运行过程以满足需求。
下面分别介绍 BoundSql
提供的三个主要的属性:sql
、parameterObject
、parameterMappings
parameterObject
为参数本身,可以传递简单对象、POJO、或者 Map、
@Param
注解的参数关于它的一些规则:
- 传递简单对象时: 会将基本类型参数装箱为对应的对象来传递
- 传递 POJO 或 Map:
parameterObject
就是对应的对象 - 传递多个对象: 如果没有
@Param
注解,会把parameterObject
变为一个Map<String, Object>
对象,键值关系按顺序规划,其对应的键为:1, 2, 3 … 或是 param1, param2, param3 … - 使用
@Param
注解: 类似与 [传递多个对象] 的规则,只是把数字的键值换成了注解的键值
parameterMappings
是一个
List
,每一个元素都是ParameterMapping
对象,描述了参数的属性:属性名、表达式、javaType、jdbcType等重要信息。通过它就可以实现参数和 SQL 的结合,使得PreparedStatement
可以通过它找到parameterObject
对象的属性设置参数,使得程序正常运行。sql
书写在映射器中被
SqlSource
解析后的 SQL,大部分情况下不需要修改它,在使用插件的时候可以根据情况来进行改写。
SqlSession 的执行过程
SqlSession
是 MyBatis 底层运行的核心,是一个接口,给出了查询、插入、更新、删除的方法(MyBatis 目前建议使用 Mapper
)。
映射器的动态代理
SqlSession
默认的实现类 DefaultSqlSession
中的 getMapper
方法:
可以看到,方法调用了 configuration
的 getMapper
方法。
我们可以继续追踪到对应的方法:
这里使用了映射器的注册器 mapperRegistry
来获取对应的接口对象。
knowMappers
是一个 HashMap,存储了注册的 mapper 接口完全限定名和对应的代理工厂。如果没有注册就会抛出异常,如果有,就会启用 MapperProxyFactory
工厂来生成一个代理实例。
我们深入 MapperProxy
源码中会发现:
类中 invoke
方法的逻辑为:
如果 Mapper 是一个 JDK 动态代理对象,就会运行到 invoke
方法里面。否则,就会通过 cachedMapperMethod
方法生成 MapperMethod
对象,最后执行 execute
方法。
深入 execute 方法中,会发现代码非常复杂,调用了很多其他的方法,从中我们可以看到一些细节,那就是最终都是通过 SqlSession
对象来运行 SQL的。MyBatis 根据 XML 中的全限定名和 SQL id 指向的方法,将其和代理对象绑定起来,通过动态代理,让接口运行起来,最后使用 SqlSession 接口的方法使得它可以执行对应的 SQL。
SqlSession 下的四大对象
映射器就是一个动态代理对进入到了 MapperMethod
的 execute
方法,经过简单的判断进入了 SqlSession
的 delete
、update
、insert
、select
等方法,而这些方法的如何执行就是正确编写插件的根本。
SqlSession
的执行过程是通过 Executor
、StatementHandler
、ParameterHandler
和 ResultSetHandler
来完成数据库操作和结果返回的。
Executor
执行器,它来调度
StatementHandler
、ParameterHandler
和ResultSetHandler
等来执行对应的 SQL,其中c
是最重要的。StatementHandler
使用数据库的
Statement(PreparedStatement)
执行操作,是这四个对象的核心,很多重要的插件都是通过拦截它来实现的。ParameterHandler
处理 SQL 参数
ResultSetHandler
对数据集(ResultSet)进行封装返回处理的,比较复杂,我们也不太常用它。
Executor —— 执行器
SqlSession
是一个门面,真正干活的是执行器,它是真正执行 Java 和数据库交互的对象,非常重要。
MyBatis 有三种执行器:
- SIMPLE —— 简易执行器(默认的执行器)
- REUSE —— 执行重用预处理语句的执行器
- BATCH —— 执行器重用语句、批量更新,批量专用的执行器
Executor
是在 Configuration
中创建的:
会根据配置的类型来创建对应的 Executor
,在创建对象之后,会执行 executor = (Executor) interceptorChain.pluginAll(executor);
这样一句代码来配置插件,这也正是插件的原理。
在 SqlSession
中,执行数据库的操作为:
我们会发现实际上执行的正是 executor
的 query
方法,以 SimpleExecutor
为例,它的 query
方法中调用了 doQuery
方法,在这个方法里,才轮到 StatementHandler
来执行。
MyBatis 先根据 Configuration
来构建 StatementHandler
,然后使用 prepareStatement
方法,对 SQL 编译和参数进行初始化。
先调用 prepare()
进行预编译和基础的设置,之后使用 StatemantHandler
的 parameterize()
来设置参数,在最后执行 StatemantHandler
的 query()
方法。
我们将焦点移至 StatementHandler
对象上。
SatementHandler —— 数据库会话
在 Configuration
类中创建 StatementHandler
的方法为:
创建的真实对象为 RoutingStatementHandler
,它实现了接口 StatementHandler
。
而 RoutingStatementHandler
也是通过适配模式来找到对应的 StatementHandler
来执行的:
从代码中可以看到,它也分三种:SimpleStatementHandler
、PreparedStatementHandler
、CallableStatementHandler
,分别对应 JDBC 的 Statement
、PreparedStatement
(预处理编译)、CallableStatement
(存储过程处理)。
根据上述的代码追溯过程,我们可以看到一条查询 SQL 的执行过程: Executor
先调用 StatementHandler
的 prepare()
方法预编译 SQL,并设置一些基本运行的参数。之后调用 parameterize()
方法启用 ParameterHandler
设置参数,完成预编译,执行查询,update()
也是同样的流程,如果是查询,MyBatis 会使用 ResultSetHandler
封装结果返回给调用者。
ParameterHandler —— 参数处理器
参数处理器的接口非常简单,接口定义为:
getParameterObject()
返回参数的对象setParameters()
设置预编译 SQL 语句的参数
ResultSetHandler —— 结果处理器
接口定义为:
handleOutputParameters()
方法是处理存储过程输出参数的
handleResultSets()
方法则用来包装结果集。