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/xpath/index.asp
”
在 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/zh/configuration.html#settings 详细了解。这里介绍几个平常使用过程中比较重要的配置
<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
标签下子标签的解析过程。
SqlSession
在 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
MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy
来和对应的 SQL 语句进行绑定的。下面我们就来解释一下绑定过程
这就是 MyBatis 的核心绑定流程,我们可以看到 SqlSession 首先调用 getMapper
方法,我们刚才说到 SqlSession 是大哥级别的人物,只定义标准(有一句话是怎么说的来着,一流的企业做标准,二流的企业做品牌,三流的企业做产品)。
SqlSession 不愿意做的事情交给 Configuration
这个手下去做,但是 Configuration 也是有小弟的,它不愿意做的事情直接甩给小弟去做,这个小弟是谁呢?它就是 MapperRegistry
,马上就到核心部分了。MapperRegistry 相当于项目经理,项目经理只从大面上把握项目进度,不需要知道手下的小弟是如何工作的,把任务完成了就好。最终真正干活的还是 MapperProxyFactory
。看到这段代码 Proxy.newProxyInstance ,你是不是有一种恍然大悟的感觉,如果你没有的话,建议查阅一下动态代理的文章,这里推荐一篇 (https://www.jianshu.com/p/95970b089360)
也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是通过动态代理来完成的。
通过动态代理,我们就可以方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操作了。那么具体的执行过程是怎么样呢?上面只是绑定过程,别着急,下面就来探讨一下 SQL 语句的执行过程。
有一部分代码被遮挡,代码有些多,不过不影响我们看主要流程
”
MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute
方法比较长,其实逻辑比较简单,就是判断是 插入
、更新
、删除
还是 查询
语句,其中如果是查询的话,还会判断返回值的类型,我们可以点进去看一下都是怎么设计的。
很多代码其实可以忽略,只看我标出来的重点就好了,我们可以看到,不管你前面经过多少道关卡处理,最终都逃不过 SqlSession
这个老大制定的标准。
我们以 selectList
为例,来看一下下面的执行过程。
这是 DefaultSqlSession
中 selectList 的代码,我们可以看到出现了 executor
,这是什么呢?我们下面来解释。
Executor
还记得我们之前的流程中提到了 Executor(执行器)
这个概念吗?我们来回顾一下它第一次出现的位置。
由 Configuration 对象创建了一个 Executor
对象,这个 Executor 是干嘛的呢?下面我们就来认识一下