目录
概述
架构设计
Mybatis的调用链路简介
构件描述
总体流程
源码剖析
读取xml配置文件
源码分析-执⾏SQL流程
先简单介绍SqlSession :
源码剖析-executor
源码剖析-StatementHandler
Mapper代理⽅式:
源码剖析-getmapper()
概述
之前的文章写过一篇自定义持久层框架,实现了Mybatis相似的功能。如果想快速掌握mybatis,强烈推荐简单阅读一下这篇文章。
自定义持久层框架
源码地址
https://gitee.com/li_gang_x/my-project-model
有了上面的基础,阅读mybatis源码就不那么陌生了,你会有些许熟悉,似曾相识的感觉。下面我将带领读者进入到mybatis框架,领略大佬们是怎么实现这个持久层框架的,怎么优雅的实现。废话不多说,下面进入主题。
架构设计
要想 整体把握一个框架,不管要属性怎么使用,而且要知其所以然。整体把握一个框架,首先就要对框架整体有一个认知。
我们把 Mybatis 的功能架构分为三层: (1) API 接⼝层:提供给外部使⽤的接⼝ API ,开发⼈员通过这些本地 API 来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。 MyBatis 和数据库的交互有两种⽅式:- a. 使⽤传统的MyBatis提供的API ;
- b. 使⽤Mapper代理的⽅式(生成代理对象进行实现)
Mybatis的调用链路简介
构件描述
- SqlSession
作为MyBatis
⼯作的主要顶层
API
,表示和数据库交互的会话,完成必要数
据库增删改查功能
- Executor
MyBatis执⾏器,是
MyBatis
调度的核⼼,负责
SQL
语句的⽣成和查询缓存的维护
- StatementHandler
封装了JDBC Statement
操作,负责对
JDBC statement
的操作,如设置参数、将Statement 结果集转换成List
集合。
- ParameterHandler
负责对⽤户传递的参数转换成JDBC Statement
所需要的参数,
- ResultSetHandler
负责将JDBC
返回的
ResultSet
结果集对象转换成
List
类型的集合;
- TypeHandler
负责java
数据类型和
jdbc
数据类型之间的映射和转换
- MappedStatement
MappedStatement维护了⼀条<
select | update | delete | insert
>节点的封装
- SqlSource
负责根据用户传递的parameterObject,动态生成Sql语句,将信息封装到BoundSql对象中并返回
- BoundSql
表示动态⽣成的SQL
语句以及相应的参数信息
总体流程
- 加载配置并初始化
- 接收调⽤请求
- 处理操作请求
- 返回处理结果
(1)
加载配置并初始化
触发条件:
加载配置⽂件
配置来源于两个地⽅,
⼀个是配置⽂件
(
主配置⽂件
conf.xml,mapper
⽂件
*.xml),
—
个是
java
代码中的
注解,
将
主配置⽂件
内容解析封装到
Configuration
,
将
sql的配置信息
加载成为⼀个
mappedstatement
对象,存储在内存之中
(2)
接收调⽤请求
触发条件
:调⽤
Mybatis
提供的
API
传⼊参数:为
SQL
的
ID
和传⼊参数对象
处理过程:
将请求传递给下层的请求处理层进⾏处理。
(3)
处理操作请求
触发条件:
API
接⼝层传递请求过来
传⼊参数:为
SQL
的
ID
和传⼊参数对象
处理过程:
- (A) 根据SQL的ID查找对应的MappedStatement对象。
- (B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
- (C) 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
- (D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理结果。
- (E) 释放连接资源。
(4)
返回处理结果
将最终的处理结果返回
源码剖析
读取xml配置文件
public class SqlSessionFactoryBuilder {
//SqlSessionFactoryBuilder有9个build()方法
//发现mybatis文档老了,http://www.mybatis.org/core/java-api.html,关于这块对不上
//以下3个方法都是调用下面第4种方法
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//第4种方法是最常用的,它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
//可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
//如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//委托XMLConfigBuilder来解析xml文件,并构建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
//这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//以下3个方法都是调用下面第8种方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//第8种方法和第4种方法差不多,Reader换成了InputStream
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
- SqlSession
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
- TypeHandler
- MappedStatement
- SqlSource
负责根据用户传递的parameterObject,动态生成Sql语句,将信息封装到BoundSql对象中并返回
- BoundSql
(1) 加载配置并初始化 触发条件: 加载配置⽂件 配置来源于两个地⽅, ⼀个是配置⽂件 ( 主配置⽂件 conf.xml,mapper ⽂件 *.xml), — 个是 java 代码中的 注解, 将 主配置⽂件 内容解析封装到 Configuration , 将 sql的配置信息 加载成为⼀个 mappedstatement 对象,存储在内存之中 (2) 接收调⽤请求 触发条件 :调⽤ Mybatis 提供的 API 传⼊参数:为 SQL 的 ID 和传⼊参数对象 处理过程: 将请求传递给下层的请求处理层进⾏处理。 (3) 处理操作请求 触发条件: API 接⼝层传递请求过来 传⼊参数:为 SQL 的 ID 和传⼊参数对象 处理过程:
- 加载配置并初始化
- 接收调⽤请求
- 处理操作请求
- 返回处理结果
- (A) 根据SQL的ID查找对应的MappedStatement对象。
- (B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
- (C) 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
- (D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理结果。
- (E) 释放连接资源。
源码剖析
读取xml配置文件
public class SqlSessionFactoryBuilder {
//SqlSessionFactoryBuilder有9个build()方法
//发现mybatis文档老了,http://www.mybatis.org/core/java-api.html,关于这块对不上
//以下3个方法都是调用下面第4种方法
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//第4种方法是最常用的,它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
//可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
//如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//委托XMLConfigBuilder来解析xml文件,并构建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
//这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//以下3个方法都是调用下面第8种方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//第8种方法和第4种方法差不多,Reader换成了InputStream
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
public class SqlSessionFactoryBuilder {
//SqlSessionFactoryBuilder有9个build()方法
//发现mybatis文档老了,http://www.mybatis.org/core/java-api.html,关于这块对不上
//以下3个方法都是调用下面第4种方法
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//第4种方法是最常用的,它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
//可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
//如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//委托XMLConfigBuilder来解析xml文件,并构建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
//这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//以下3个方法都是调用下面第8种方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//第8种方法和第4种方法差不多,Reader换成了InputStream
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
有通过Reader获取和通过Io流获取的两种方法,下面主要写通过输入流获取的方法
Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
//这⼀⾏代码正是初始化⼯作的开始。
SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
//第8种方法和第4种方法差不多,Reader换成了InputStream
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
MyBatis
在初始化的时候,会将
MyBatis
的配置信息全部加载到内存中,使⽤
org.apache.ibatis.session.Configuration
实例来维护
下⾯进⼊对配置⽂件解析部分:
⾸先对
Configuration
对象进⾏介绍:
Configuration 对象的结构和 xml 配置⽂件的对象⼏乎相同。 回顾⼀下 xml 中的配置标签有哪些: properties ( 属性 ) , settings ( 设置 ) , typeAliases ( 类型别名 ) , typeHandlers ( 类型处理 器 ) , objectFactory ( 对象⼯⼚ ) , mappers ( 映射器 ) 等 Configuration 也有对应的对象属性来封 装它们 也就是说,初始化配置⽂件信息的本质就是创建 Configuration 对象,将解析的 xml 数据封装到 Configuration 内部属性中
//解析配置
public Configuration parse() {
//如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//根节点是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
介绍⼀下 MappedStatement : 作⽤: MappedStatement 与 Mapper 配置⽂件中的⼀个 select/update/insert/delete 节点相对应。 mapper 中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条 SQL 语句。 初始化过程: 回顾刚开始介绍的加载配置⽂件的过程中,会对 mybatis-config.xm l 中的各个标签都进⾏解析,其中有mappers 标签⽤来引⼊ mapper.xml ⽂件或者配置 mapper 接⼝的⽬录。
这样的⼀个select标签会在初始化配置⽂件时被解析封装成⼀个MappedStatement对象,然后存储在 Configuration对象的mappedStatements属性中,mappedStatements 是⼀个HashMap,存储时
key =全限定类名+⽅法名,
value =对应的MappedStatement对象。
- 在configuration中对应的属性为
Map在 XMLConfigBuilder 中的处理:mappedStatements = new StrictMap ("Mapped Statements collection")
private void parseConfiguration(XNode root) {
try {
//省略其他标签的处理
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration.Cause:" + e, e);
}
}
到此对
xml
配置⽂件的解析就结束了,回到步骤
2.
中调⽤的重载
build
⽅法
// 5.调⽤的重载⽅法
public SqlSessionFactory build(Configuration config) {
//创建了 DefaultSqlSessionFactory 对象,传⼊ Configuration 对象。
return new DefaultSqlSessionFactory(config);
}
源码分析-执⾏SQL流程
先简单介绍SqlSession :
SqlSession
是⼀个接⼝,
它有两个实现类:
DefaultSqlSession (
默认
)
和 SqlSessionManager (弃⽤,不做介绍
)
SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀ 个SqlSession,并且在使⽤完毕后需要close
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
...
}
SqlSession
中的两个最重要的参数,
configuration
与初始化时的相同,
Executor
为执⾏器
Executor
:
Executor
也是⼀个接⼝,他有三个常⽤的实现类:
- BatchExecutor (重⽤语句并执⾏批量更新)
- ReuseExecutor (重⽤预处理语句 prepared statements)
- SimpleExecutor (普通的执⾏器,默认)
继续分析,初始化完毕后,我们就要执⾏
SQL
了
首先获取sqlSession
SqlSession sqlSession = factory.openSession();
List list =
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
...
}
SqlSession
中的两个最重要的参数,
configuration
与初始化时的相同,
Executor
为执⾏器
Executor
:
Executor
也是⼀个接⼝,他有三个常⽤的实现类:
- BatchExecutor (重⽤语句并执⾏批量更新)
- ReuseExecutor (重⽤预处理语句 prepared statements)
- SimpleExecutor (普通的执⾏器,默认)
SqlSession sqlSession = factory.openSession(); Listlist = sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");
进入到openSession方法
//6. 进⼊ openSession ⽅法。
public SqlSession openSession() {
//getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null,
false);
}
可以看到调用的是openSessionFormDataSource
//7. 进⼊ penSessionFromDataSource 。 //ExecutorType 为 Executor 的类型, TransactionIsolationLevel 为事务隔离级别, autoCommit 是否开启事务 //openSession 的多个重载⽅法可以指定获得的 SeqSession 的 Executor 类型和事务的处理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);
//然后产生一个DefaultSqlSession
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();
}
}
执⾏
sqlsession
中的
api
//8.进⼊selectList⽅法,多个重载⽅法 @Override publicList selectList(String statement) { return this.selectList(statement, null); } @Override public List selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } //核心selectList @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根据statementId(全限定名+⽅法名)找到对应的MappedStatement 根据传⼊的全限定名+⽅法名从映射的Map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); //调⽤Executor中的⽅法处理 //转而用执行器来查询结果,注意这里传入的ResultHandler是null //RowBounds是⽤来逻辑分⻚ // wrapCollection(parameter)是⽤来装饰集合或者数组参数 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
源码剖析-executor
继续源码中的步骤,进⼊ executor.query()//SqlSession.selectList会调用此方法 // 继续源码中的步骤,进⼊executor.query() @Override public进⼊ query 的重载⽅法中List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根据传⼊的参数动态获得SQL语句,最后返回⽤BoundSql对象表示 BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存Key 为本次查询创建缓存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@Override publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); //如果已经关闭,报错 if (closed) { throw new ExecutorException("Executor was closed."); } //先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { //加一,这样递归调用到上面的时候就不会再清局部缓存了 queryStack++; //先根据cachekey从localCache去查 list = resultHandler == null ? (List ) localCache.getObject(key) : null; if (list != null) { //若查到localCache缓存,处理localOutputParameterCache handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //从数据库查 如果缓存中没有本次查找的值,那么从数据库中查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { //清空堆栈 queryStack--; } if (queryStack == 0) { //延迟加载队列中所有元素 for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 //清空延迟加载队列 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 //如果是STATEMENT,清本地缓存 clearLocalCache(); } } return list; }
下面进入到从数据库查询的逻辑
//从数据库查 privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; //先向缓存中放入占位符??? localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //查询的⽅法 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { //最后删除占位符 localCache.removeObject(key); } //将查询结果放⼊缓存 localCache.putObject(key, list); //如果是存储过程,OUT参数也加入缓存 if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
这里有个doQuery 方法阅读过源码的同学都知道,do开头的一般是真正执行逻辑的方法
//query-->queryFromDatabase-->doQuery protected abstractList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
这个方法是protected修饰的,是子类实现
SimpleExecutor 中实现⽗类的 doQuery 抽象⽅法//select @Override publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //新建一个StatementHandler //这里看到ResultHandler传入了 //传⼊参数创建StatementHandler对象来执⾏查询 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //准备语句 创建jdbc中的statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); //StatementHandler.query return handler. query(stmt, resultHandler); } finally { closeStatement(stmt); } }
下面查看prepareStatement方法的实现
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//代码中的getConnection⽅法经过重重调⽤最后会调⽤openConnection⽅法,从连接池中获得连接
Connection connection = getConnection(statementLog);
//调用StatementHandler.prepare
stmt = handler.prepare(connection);
//调用StatementHandler.parameterize
handler.parameterize(stmt);
return stmt;
}
这里的连接是 JdbcTransaction 中从连接池中获取的连接
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//从连接池获得连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
上述的Executor.query()⽅法⼏经转折,最后会创建⼀个StatementHandler对象,然后将必要的参数传递给 StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。
从上⾯的代码中我们可以看出, Executor 的功能和作⽤是:(1 、根据传递的参数,完成 SQL 语句的动态解析,⽣成 BoundSql 对象,供 StatementHandler 使⽤; (2 、为查询创建缓存,以提⾼性能 (3 、创建 JDBC 的 Statement 连接对象,传递给 *StatementHandler* 对象,返回 List 查询结果
源码剖析-StatementHandler
StatementHandler 对象主要完成两个⼯作:- 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包 含若⼲个?占位符,我们其后再对占位符进⾏设值。StatementHandler通过 parameterize(statement)⽅法对 Statement 进⾏设值;
- StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)⽅法来 完成执⾏Statement,和将Statement对象返回的resultSet封装成List;
进⼊到 StatementHandler 的 parameterize(statement)⽅法的实现:
//参数化
void parameterize(Statement statement)
throws SQLException;
这里将参数进行替换设置
这个接口的实现是
PreparedStatementHandler 预处理语句处理器(PREPARED)
@Override
public void parameterize(Statement statement) throws SQLException {
//调用ParameterHandler.setParameters
parameterHandler.setParameters((PreparedStatement) statement);
}
下面是设置参数的具体实现
//设置参数
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//循环设参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
//如果不是OUT,才设进去
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
//若有额外的参数, 设为额外的参数
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//若参数为null,直接设null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//若参数有相应的TypeHandler,直接设object
value = parameterObject;
} else {
//除此以外,MetaObject.getValue反射取得值设进去
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 每⼀个 Mapping都有⼀个 TypeHandler,根据 TypeHandler 来对preparedStatement 进⾏设置参数
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
//不同类型的set方法不同,所以委派给子类的setParameter方法
jdbcType = configuration.getJdbcTypeForNull();
}
//设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
从上述的代码可以看到
,StatementHandler
的
parameterize(Statement)
⽅法调⽤了
ParameterHandler
的
setParameters(statement)
⽅法,
ParameterHandler
的
setParameters(Statement )
⽅法负责根据我们输⼊的参数,对
statement
对象的 ?占位符处进⾏赋值。
进⼊到
StatementHandler
的
List query(Statement statement, ResultHandler resultHandler)
⽅法的
实现:
@Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { // 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处理 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); //2.使⽤ ResultHandler 来处理 ResultSet return resultSetHandler. handleResultSets(ps); }
也是PreparedStatementHandler实现的方法
从上述代码我们可以看出, StatementHandler 的 List query(Statement statement, ResultHandler resultHandler) ⽅法的实现,是调⽤了 ResultSetHandler 的 handleResultSets(Statement) ⽅法。 ResultSetHandler 的 handleResultSets(Statement) ⽅法会将 Statement 语句执⾏后⽣成的 resultSet 结果集转换成List 结果集合 这里是 DefaultResultSetHandler 默认的结果处理器@Override public List
Mapper代理⽅式:
public static void main(String[] args) {
//前三步都相同
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
//这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法。
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List list = mapper.getUserByName("tom");
}
思考⼀个问题,通常的
Mapper
接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢?
答案很简单动态 代理
开始之前介绍⼀下
MyBatis
初始化时对接⼝的处理:
MapperRegistry
是
Configuration
中的⼀个属性, 它内部维护⼀个HashMap
⽤于存放
mapper
接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。
mappers
中可以配置接⼝的包路径,或者某个具体的接⼝类。
当解析 mappers 标签时,它会判断解析到的是 mapper 配置⽂件时,会再将对应配置⽂件中的增删 改 查标签 封装成MappedStatement 对象,存⼊ mappedStatements 中。 ( 上⽂介绍了 ) 当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory 对象,存⼊ HashMap 中, key = 接⼝的字节码对象, value = 此接⼝对应的MapperProxyFactory 对象
源码剖析-getmapper() 进⼊ sqlSession.getMapper(UserMapper.class ) 中
映射器注册机
DefaultSqlSession默认的sqlSession
@Override publicT getMapper(Class type) { //最后会去调用MapperRegistry.getMapper return configuration. getMapper(type, this); }
调用的是Configuration中的getMapper
publicT getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
最终调用的是MapperRegistry 中的 getMapper
//返回代理类 publicT getMapper(Class type, SqlSession sqlSession) { //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //通过动态代理⼯⼚⽣成示例 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
这里的newInstance 方法如下
MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
//创建了 JDK动态代理的Handler类
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
//调⽤了重载⽅法
return newInstance(mapperProxy);
}
下面是调用的JDK的动态代理
protected T newInstance(MapperProxy/ 构造,传⼊了 SqlSession ,说明每个 session 中的代理对象的不同的!mapperProxy) { //用JDK自带的动态代理生成映射器 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
public class MapperProxy在动态代理返回了示例后,我们就可以直接调⽤ mapper 类中的⽅法了, 但代理对象调⽤⽅法,执⾏是在MapperProxy 中的 invoke ⽅法中implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的! public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } ...省略部分源码,剩余的在下面 }
//如果是Object定义的⽅法,直接调⽤
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//重点在这:MapperMethod最终调⽤了执⾏的⽅法
return mapperMethod.execute(sqlSession, args);
}
MapperMethod最终调⽤了 mapperMethod.execute(sqlSession, args); 执⾏的⽅法
public class MapperProxyimplements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法 //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //这里优化了,去缓存中找MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); //执行 return mapperMethod.execute(sqlSession, args); } //去缓存中找MapperMethod private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { //找不到才去new mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod进⼊execute⽅法:
方法类别的判断方法,在构造方法中初始化
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
//执行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
//判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch(command.getType()) {
if (SqlCommandType.INSERT == command.getType()) {
//转换参数
Object param = method.convertArgsToSqlCommandParam(args);
//执⾏INSERT操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
//转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
//转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
//⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
//执⾏查询,返回列表
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
//执⾏查询,返回Map
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录 查询单条
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
//返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
//这个方法对返回值的类型进行了一些检查,使得更安全
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
//如果返回值是大int或小int
result = Integer.valueOf(rowCount);
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
//如果返回值是大long或小long
result = Long.valueOf(rowCount);
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
//如果返回值是大boolean或小boolean
result = Boolean.valueOf(rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
下面是参数转换成sql 语句中的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
//如果没参数
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//如果只有一个参数
return args[params.keySet().iterator().next().intValue()];
} else {
//否则,返回一个ParamMap,修改参数名,参数名就是其位置
final Map param = new ParamMap();
int i = 0;
for (Map.Entry entry : params.entrySet()) {
//1.先加一个#{0},#{1},#{2}...参数
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
//2.再加一个#{param1},#{param2}...参数
//你可以传递多个参数给一个映射器方法。如果你这样做了,
//默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
//如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
好了Mybatis执行流程就讲到这了。后面会对部分进细节行更加详细的讲述
如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。



