MyBatis 流程图
整个流程可以分为两个阶段:
- 环境的初始化
SqlSessionFactoryBuilder对象使用build(Configuration configuration),创建SqlSessionFactory对象;
XMLConfigBuilder解析XML配置文件;
创建Configuration对象(非常重要Mybatis上下文对象,存放了Mybatis的全局配置);
SqlSessionFactoryBuilder对象使用build(Configuration configuration),创建SqlSessionFactory对象;
2.执行sql语句
SqlSessionFactory可以使用openSession()方法返回一个SqlSession;
SqlSession调用一些api,产生一个执行器Executor,
Executor中会创建一个StatementHandler去执行sql语句通过execute()返回结果集,
交给ResultSetHandler处理结果集和pojo的映射。
一 环境的初始化:
1加载资源
1.1配置文件Configuration.xml :
该配置文件是 MyBatis 的全局配置文件,在这个文件中可以配置诸多项目。常用的内容是别名设置,拦截器设置等。 有以下属性:
1.1.1Properties(属性)
将数据库连接参数单独配置在 db.properties 中,放在类路径下。这样只需要在 SqlMapConfig.xml 中加载 db.properties 的属性值。这样在 SqlMapConfig.xml 中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在 db.properties 中,原因:方便对参数进行统一管理 。
1.1.2 Settings(全局配置参数)
Mybatis 全局配置参数,全局参数将会影响 mybatis 的运行行为。比如:开启二级缓存、开启延迟加载
1.1.3TypeAliases(类型别名)
类型别名是为 Java 类型命名一个短的名字。它只和 XML 配置有关, 只用来减少类完全限定名的多余部分
1.1.4Plugins(插件)
MyBatis 允许你在某一点拦截已映射语句执行的调用。默认情况下,MyBatis 允许使用插件来拦截方法调用
1.1.5Environments(环境集合属性对象)
MyBatis 可以配置多种环境。这会帮助你将 SQL 映射应用于多种数据库之中。但是要记得一个很重要的问题:你可以配置多种环境,但每个数据库对应一个 SqlSessionFactory。
所以,如果你想连接两个数据库,你需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。
1.1.5.1 Environment(环境子属性对象)
1.1.5.1.1TransactionManager(事务管理)
在 MyBatis 中有两种事务管理器类型(也就是 type=”[JDBC|MANAGED]”)
1.1.5.1.2DataSource(数据源)
UNPOOLED|POOLED|JNDI
1.1.6Mappers(映射器)
指定映射配置文件位置
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<package name="org.mybatis.builder"/>
1.2Mapper.xml
1.2.1ResultMap
Mybatis 中可以使用 resultMap 完成高级输出结果映射。如果查询出来的列名和定义的pojo 属性名不一致,就可以通过定义一个 resultMap 对列名和 pojo 属性名之间作一个映射关系。
1.2.2Cache :开启二级缓存
1.2.3Select :查询语句
1.2.4Insert :插入语句
1.2.5Update :更新语句
1.2.6Delete :删除语句
1.2.7Sql :可以重用的 SQL 块,也可以被其他语句引用
1.3Resources
Mapper.xml 映射文件中定义了操作数据库的 sql,每个 sql 是一个 statement,映射文件是 mybatis 的核心 。
1.3 加载配置文件需要用到:
1.3.1Resources 工具类
Resources 工具类会从路径中加载资源,并返回一个输入流对象,对于资源文件的加载提供了简易的使用方法。 加载一个资源有很多方式:
- 对于简单的只读文本数据,加载为 Reader。
- 对于简单的只读二进制或文本数据,加载为 Stream。
- 对于可读写的二进制或文本文件,加载为 File。
- 对于只读的配置属性文件,加载为 Properties。
- 对于只读的通用资源,加载为 URL。
按以上的顺序,Resources 类加载资源的方法如下:
- Reader getResourceAsReader(String resource);
- Stream getResourceAsStream(String resource);
- File getResourceAsFile(String resource);
- Properties getResourceAsProperties(String resource);
- Url getResourceAsUrl(String resource);
XMLConfigBuilder
该类是 XML 配置构建者类,是用来通过 XML 配置文件来构建 Configuration 对象实例, 构建的过程就是解析 Configuration.xml 配置文件的过程,期间会将从配置文件中获取到的指定标签的值逐个添加到之前创建好的默认 Configuration 对象实例中。
1.4 介绍加载流程
String resource = "SqlMapperClient.xml";
//加载SqlMapperClient.xml,Resources工具类会从路径中加载资源,并返回一个输入流对象,对于资源文件的加载提供了简易的使用方法。
InputStream inputStream = Resources.getResourceAsStream(resource);
getResourceAsStream(String resource)方法,调用重载的getResourceAsStream(ClassLoader loader, String resource)方法。这里没有指定类加载器,会使用默认的类加载器,具体如下。
通过类加载器加载配置文件,返回InputStream字节流文件。其中调用了getResourceAsStream(String resource, ClassLoader classLoader)方法,其中类加载器 classLoader 为null。
该方法中类加载器classLoader通过 getClassLoaders(ClassLoader classLoader)方法获得默认类加载器。
这样就可以返回通过默认类加载器和给定资源文件产生的InputStream。
2 接下来使用SqlSessionFactoryBuilder根据Configuration创建SqlSessionFactory
2.1介绍相关类:
2.1.1 SqlSessionFactoryBuilder是 SqlSessionFactory(会话工厂)的构建者类。这个过程:
- 调用 XMLConfigBuilder 类的构造器来创建一个 XML 配置构建器对象,
- 利用这个构建器对象(XMLConfigBuilder)来调用其解析方法 parse()来完成 Configuration 对象的创建,
- 之后以这个配置对象为参数调用会话工厂构建者类SqlSessionFactoryBuilder中的 build(Configuration config)方法来完成SqlSessionFactory(会话工厂)对象的构建,实际上SQLSessionFactory是一个接口,创建的是它的实现类。
2.1.2 SQLSessionFactory是一个接口
2.1.3 DefaultSQLSessionFactory
SqlsessionFactory 该 接 口 是 会 话 工 厂 , 是 用 来 生 产 会 话 的 工 厂 接 口 , DefaultSqlSessionFactory 是其实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。
openSessionFromDataSource方法构建了默认的SqlSession对象。
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();
}
}
2.1.4 DefaultSqlSession:
- 实现了SqlSesson接口的相关方法,
- 设置了基本连接参数,
- 封装了常见的对数据库的操作如增删改查
2.1.5 Configuration :
Configuration 是 Mybatis 的上下文对象,实例化这个类的目的就是为了使用其对象作为项目全局配置对象,这样通过配置文件配置的信息可以保存在这个配置对象中,而这个配置对象在创建好之后是保存在 JVM 的 Heap 内存中的,方便随时读取。不然每次需要配置信息的时候都要临时从磁盘配置文件中获取,代码复用性差的同时,也不利于开发 。作为全局上下文,只有一个实例。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
部分属性:使用聚合的方式将各个节点聚合到了configuration对象中。
构造方法:
给很多属性起了别名,这样可以在配置文件中用别名控制事务
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
2.2 流程详细分析:
首先build(InputStream inputStream)方法调用build(InputStream inputStream, String environment, Properties properties)方法,需要传递配置文件的文件流InputStream。
接下来被调用的build(InputStream inputStream, String environment, Properties properties)方法要做:
- 实例化Configuration对象
- 解析配置文件并装入 Configuration 对象
- build(Configuration config)生成DefaultSQLSessionFactory对象(单例模式)
2.2.1 实例化Configuration对象
这里首先调用 XMLConfigBuilder 类的构造方法来创建一个 XML 配置构建器对象XMLConfigBuilder:创建mybaitis全局配置文件解析器XPathParser并调用重载的构造方法XMLConfigBuilder(XPathParser parser, String environment, Properties props)。
在XMLConfigBuilder(XPathParser parser, String environment, Properties props)中,调用父类的构造方法实例化Configuration对象用于用于存储解析后的文件。
2.1.2 解析配置文件并装入 Configuration 对象
调用parseConfiguration(XNode root)方法,解析 Configuration.xml 配置文件,期间会将从配置文件中获取到的指定标签的值逐个添加到之前创建好的默认 Configuration 对象实例中。 其中evalNode()方法是找到Mybatis配置文件的根节点,
解析配置文件中的各种参数,然后装入创建好的configuration对象中:
举例:解析 properties:
2.1.3 build(Configuration config)生成DefaultSQLSessionFactory对象(单例模式)
SqlSessionFactory是一个生产SqlSession的接口,因此要返回它的实现类DefaultSQLSessionFactory是一个真正生产sqlSession的工厂类(这个类的实例的生命周期是全局的,(单例模式),一直存在直到服务器关闭)
创建环境完毕,
二 执行sql语句
完成了SQLSessionFactory的创建,就可以使用DefaultSQLSessionFactory对数据库进行操作了。
相关类介绍:
Executor
执行器接口,SqlSession 会话是面向程序员的,而内部真正执行数据库操作的却是 Executor 执行器,可以将 Executor 看作是面向 MyBatis 执行环境的,SqlSession 就是门面货, Executor 才是实干家。通过 SqlSession 产生的数据库操作,全部是通过调用 Executor 执行器 来完成的。
Executor 是跟 SqlSession 绑定在一起的,每一个 SqlSession 都拥有一个新的 Executor 对 象,由 Configuration 创建。
newExecutor(Transaction transaction, ExecutorType executorType):
默认的executorType是
Executor接口:
定义了Mybatis能执行的操作。规范了能对数据库进行什么样的操作,具体实施交给StatementHandler。
Executor 继承结构 :
1.1 BaseExecutor
SimpleExecutor:(简单)
每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。(可以是 Statement 或 PrepareStatement 对象)
ReuseExecutor:(预处理)
执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。(可以是 Statement 或 PrepareStatement 对象)
BatchExecutor:(批处理)
执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 。
1.2 CachingExecutor (带缓存)
先从缓存中获取查询结果,存在就返回,不存在,再委托给 Executor delegate 去数据库取,delegate 可以是上面任一的 SimpleExecutor、ReuseExecutor、BatchExecutor。
2 StatementHandler
该类是 Statement 处理器,封装了 JDBC Statement 的各种数据库操作方法 execute(),是最终与数据库打交道的对象,使用Connection连接的数据库,可见 MyBatis 其实就是将操作数据库的 JDBC 操作封装起来的一个框架,同时还实现了 ORM 罢了。
RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据 Executor 的类型,创建不同的类型 StatementHandler。
3 ResultSetHandler
结果集处理器,如果是查询操作,必定会有返回结果,针对返回结果的操作,就要使用 ResultSetHandler 来进行处理,这个是由 StatementHandler 来进行调用的。这个处理器的作用就是对返回结果进行处理,实现了结果集和pojo的关联。
详细流程分析:
已执行selectOne(“”)为例:
SqlSessionFactory 中的 openSession()方法的到 sqlSession 调用 Executor
selectOne是SQLSession接口中的一个方法,由DefaultSqlSessionFactory实现
调用了<T>selectList(statement, parameter)方法返回list集合。
这里query方法是BaseExecutor 下的SimpleExecutor,
查看query的方法实现,实际上是有baseExecutor实现的,
接着往下,使用了queryFromDatabase方法,该
queryFromDatabase中使用了doQuery方法,执行器默认创建的BaseExecutor下的SimpleExecutor对象,所以这里的doQuery方法是simpleExecutor下实现的。
继续追发现是由StatementHandler调用了query方法,StatementHander对象才是Mybatis中具体数据库操作的对象。
Mybatis在这里对JDBC封装,StatementHandler对象相当于JDBC操作中的Statement,
这里的StatementHandler 对象是默认的 RoutingStatementHandler,
继续追调用了SimpleStatementHandler中的query方法,
其中结果集ResultHandler接口:
DefaultResultHandler实现了接口,完成对结果集的封装。
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);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
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);
}
Sql执行结束。