在上一篇文章中,我们了解到了 Mybatis,但是它相较于原生的 JDBC 到底为我们做了哪些易用性的封装其实并没有做分析,所以在这篇文章中,我们先浅浅了解 Mybatis 到底为我们做了哪些事情(如果说的太深入,可能有些读者会有疑惑,所以我们先说表面上能看出来的,更深入的后面解析过程中都会提出来)
相较于原生的 JDBC,Mybatis 所做的事情
1、 各种资源的管理:在JDBC实例的开始,能看到获取连接,但是Mybatis的实例代码中是不需要我们写的,当然还有Statement、ResultSet…这些资源,那么可以推断出Mybatis帮我们管理的这些资源(获取和回收);
2、 解耦(可能有点宽泛,但是我已经想不到更好的描述了):将sql从代码里面拆分出来,并通过一个映射接口类(Mapper.class)作为桥梁来执行真实的sql语句,这种处理方式能使代码看上去更清晰;
3. sql语句变量赋值:将复杂且丑陋的 setXXX(idx, value) 改为可以让 sql 语句更为清晰的变量赋值,即 #{value} 或 $
{value},两者的区别后面的解析过程也会提到的。
4、 动态sql:虽然在Demo中没有体现,但是只要是用过Mybatis的人应该都觉得Mybatis的动态sql用来还是很爽的,而且也相当的强大;
5、 结果集映射:从Demo中可以看到,JDBC需要我们自己手动编写代码将sql的执行结果变为想要的实体类对象而Mybatis能自动的帮我们完成(这个可是写者在初学Mybatis最好奇的功能呢);
6、 …;
实际上这些只是写者从表面上能看出来的,其实还有很多东西可能都隐藏在背后。我们后面会一一将他们刨出来的。
Mybatis 的运行流程(生命周期)
要学习和了解一门框架,从运行流程入手是一个不错的选择。接下来要做的事就是解析 Mybatis 的运行流程。
MybatisDemo.java
// 1. 通过 mybatis 的工具类 Resources 获取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 将配置文件交给 SqlSessionFactoryBuilder 以构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 得到 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取代理后的 UserMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 5. 调用方法打印结果
userMapper.selectById(1L);
1. 得到配置信息(XML)
先读取mybatis 的配置文件,因为其中包含了 mybatis 启动必要的配置信息。
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
以下就是 mybatis 配置文档的顶层结构。
<configuration> <!-- myabtis 配置文件根节点 -->
<properties></properties> <!-- 属性 -->
<settings></settings> <!-- 设置 -->
<typeAliases></typeAliases> <!-- 类型别名 -->
<typeHandlers></typeHandlers> <!-- 类型处理器 -->
<objectFactory></objectFactory> <!-- 对象工厂 -->
<plugins></plugins> <!-- 插件 -->
<environments></environments> <!-- 环境配置 -->
<databaseIdProvider></databaseIdProvider> <!-- 数据库厂商标识 -->
<mappers></mappers> <!-- 映射器 -->
</configuration>
其中<environments>
和 <mappers>
是必须要在文档中声明的,其他的要么是有默认值,要么就是可选的,在学习使用阶段是可以暂时忽略的。
2. 构建 SqlSessionFactory
由于配置文件是 XML 格式的,在 Java 代码中想要直接从 XML 中获取关键信息的话,每次都需要去解析一遍配置文件是比较麻烦的。所以我们需要对配置文件进行解析,然后构建为一个 Java 的对象,直接对配置对象进行操作的话,会相对简单很多。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
ActorSqlSessionFactoryBuilderXMLConfigBuilderDefaultSqlSessionFactorynewbuildnewparsebuildnewActorSqlSessionFactoryBuilderXMLConfigBuilderDefaultSqlSessionFactory
简述:由于构建 DefaultSqlSessionFactory 需要 Configuration 对象,所以不能直接 new 而是要借助 SqlSessionFactoryBuilder 来构建。在内部使用的是 XMLConfigBuilder 来解析 mybatis-config.xml 文件并构建 Configuration 对象。
XMLConfigBuilder 解析流程一览
/*
可以看到解析顺序和配置文件的声明基本一致
还忽略了一些其他的 Configuration 属性的设置
*/
private void parseConfiguration(XNode root) {
// 解析 <properties> 标签
propertiesElement();
...
// 解析 <typeAliases> 标签
typeAliasesElement();
// 解析 <plugins> 标签
pluginElement();
// 解析 <objectFactory> 标签
objectFactoryElement();
...
// 解析 <settings> 标签
settingsElement();
// 解析 <environments> 标签
environmentsElement();
// 解析 <databaseIdProvider> 标签
databaseIdProviderElement();
// 解析 <typeHandlers> 标签
typeHandlerElement();
// 解析 <mappers> 标签
mapperElement();
}
3. 获取 SqlSession
SqlSession 是 Mybatis 中非常重要的一个接口,可以通过这个接口来执行命令,获取映射器示例和管理事务。
SqlSession sqlSession = sqlSessionFactory.openSession();
ActorDefaultSqlSessionFactoryDefaultSqlSessionopenSessionopenSessionFromDataSourcenewActorDefaultSqlSessionFactoryDefaultSqlSession
关键步骤解析:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
// 获取 Mybatis 的运行环境(包含事务工厂和数据源)
Environment environment = configuration.getEnvironment();
// 从运行环境中获取事务工厂
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 通过事务工厂创建事务
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器,用于执行 sql 语句
Executor executor = configuration.newExecutor(tx, execType);
// 将 Configuration、Executor 传递给了 DefaultSqlSession,所以 SqlSession 可以用于执行命令、事务管理、获取映射器等功能
return new DefaultSqlSession(configuration, executor, autoCommit);
}
4. 获取 Mapper (映射器)
Mapper 在 Java 中只是一个接口,并且没有任何实现类,所以是不能直接通过 new 来进行实例化,在 Demo 中可以看到,能拿到实例化对象,并且能够直接调用,可以推测是使用了动态代理(不清楚的建议先去 Google 一下)。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
ActorDefaultSqlSessionConfigurationMapperRegistryMapperProxyFactorygetMappergetMappergetMappernewInstance(SqlSession)newInstance(MapperProxy)ActorDefaultSqlSessionConfigurationMapperRegistryMapperProxyFactory
关键步骤解析:
protected T newInstance(MapperProxy<T> mapperProxy) {
// 可以看到最底层使用的是 JDK 的动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
mapperInterface }, mapperProxy);
}
5. 调用映射器方法
在上一小节说到,映射器的实现类是通过 JDK 动态代理生成的,所以想要知道,在调用映射器方法的时候做了哪些事,就需要看 MapperProxy(实现了 InvocationHandler ) 中 invoke()
方法的处理逻辑就好了。
userMapper.selectById(1L);
ActorUserMapperMapperProxyPlainMethodInvokerMapperMethodSqlSessionselectByIdinvokecachedInvokerinvokeexecuteinsert,update,delete,selectActorUserMapperMapperProxyPlainMethodInvokerMapperMethodSqlSession
关键步骤解析:可以看到,实质上还是使用的是 SqlSession 执行命令的能力,只是进行了层层封装。