Mybatis架构设计
Mybatis功能架构分为三层:
- API接口层:提供给外部使用的接口API, 开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- mybatis和数据库的交互方式有两种:使用传统的mybatis提供的API以及使用Mapper代理的方式。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 框架支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。
主要构件及相互关系
| 构建 | 描述 | |||
|---|---|---|---|---|
| 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语句以及相应的参数信息 |
总体执行流程
Mybatis源码分析
传统方式源码剖析
传统的方式使用Mybatis如下:
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
List<Order> orderAndUser = mapper.findOrderAndUser();
for (Order order : orderAndUser) {
System.out.println(order);
}
Resources就不必多说了就是将配置文件转换为InputStream。
SqlSessionFactoryBuilder
我们先看SqlSessionFactoryBuilder做了什么? 关键源码如下:
主要做了两步:
- 调用
XMLConfigBuilder解析XML文件,将解析后的信息存储到Configuration中 - 创建
SqlSessionFactory对象DefaultSqlSessionFactory并且将Configuration最为参数传入
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XML 解析
// 创建 DefaultSqlSessionFactory 对象
return build(parser.parse());
.....
}
/**
* 3. 创建 DefaultSqlSessionFactory 对象
*
* @param config Configuration 对象
* @return DefaultSqlSessionFactory 对象
*/
public SqlSessionFactory build(Configuration config) {
// 构建者设计模式
return new DefaultSqlSessionFactory(config);
}
下面我们来看一下解析XMLXMLConfigBuilder的关键代码:
其实就是解析XML配置的标签的内容:
properties (属性),settings(设置),typeAliases(类型别名),typeHandlers(类型处理器),objectFactory(对象工厂),mappers映射器等。这些信息都会保存在Configuration对象的属性中。
/**
* 解析 XML 成 Configuration 对象。
*
* @return Configuration 对象
*/
public Configuration parse() {
// 若已解析,抛出 BuilderException 异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记已解析
parsed = true;
///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
// 解析 XML configuration 节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 解析 XML
*
* 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
*
* @param root 根节点
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 <properties /> 标签
propertiesElement(root.evalNode("properties"));
// 解析 <settings /> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义的 VFS 实现类
loadCustomVfs(settings);
// 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 <plugins /> 标签
pluginElement(root.evalNode("plugins"));
// 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 <environments /> 标签
environmentsElement(root.evalNode("environments"));
// 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
另外还需要关注的一个重点就是<mappers/>标签的解析,Mapper映射文件中的每一个节点都会解析成MappedStatement,最终会会将MapppedStatement存储到Configuration对象的mappedStatements属性中它是一个Map集合,注意key是由namespace + id 属性组成
/**
* MappedStatement 映射
*
* KEY:`${namespace}.${id}`
*/
protected final Map<String, MappedStatement> mappedStatements =
new StrictMap<>("Mapped Statements collection");
xml配置文件结束后,会返回一个DefaultSessionFactory对象实例。
执行SQL操作分析
当我们得到一个SessionFactory对象之后,会继续执行如下代码:
会得到SqlSession对象,在上述的介绍Mybatis的架构中,我们知道SqlSession对象是Mybatis中用于和数据库交互的顶层类
,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要Close
SqlSession sqlSession = sqlSessionFactory.openSession();
下面我们来看一下具体的openSession方法的实现如下:
openSession方法会调用openSessionFromDataSource 方法:
其实最主要的就是四步:
- 获得
Environment对象,其实这个对象就是配置文件中配置的<environment>标签配置的属性,它里面配置了DataSource的数据源的信息 - 创建事务对象
- 获取执行器
Executor对象,Executor是非常重要的类,也是Mybatis的核心 - 创建SqlSession对象
DefaultSqlSession,同时配置信息和执行器都会作为参数传入
//6. 进入openSession方法
@Override
public SqlSession openSession() {
//getDefaultExecutorType()传递的是默认的SimpleExecutor 执行器 level 数据库事务的隔离级别 autoCommit 表示事务是否自动提交
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//7. 进入openSessionFromDataSource。
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 1. 获得 Environment 对象
final Environment environment = configuration.getEnvironment();
// 2. 创建 Transaction 对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 3. 创建 Executor 执行器 对象
final Executor executor = configuration.newExecutor(tx, execType);
// 4. 创建 DefaultSqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果发生异常,则关闭 Transaction 对象
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
下面我们来看一下Executor执行器的创建过程,主要在Configuration类中创建
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//openSession() 无参的方法会调用Configuration的getDefaultExecutorType方法获取默认的执行器
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
/**
* 创建 Executor 对象
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 获得执行器类型
executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
// 创建对应实现的 Executor 对象
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 重用语句并执行批量更新
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 重用预处理语句 prepared statements
executor = new ReuseExecutor(this, transaction);
} else {
// 普通的执行器,默认
executor = new SimpleExecutor(this, transaction);
}
// 如果开启缓存,创建 CachingExecutor 对象,进行包装
if (cacheEnabled) {
// 管理缓存相关的执行器
executor = new CachingExecutor(executor);
}
// 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
通过上述代码我们知道了最终返回的执行器对象是SimpleExecutor 这个对象实例。SqlSession对象执行的方法最终都会落实到Executor中执行,下面来看一下DefaultSqlSession的实现类,例如查询方法:
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 获得 MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行查询 交个executor执行
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
例如更新方法(包括新增、更新、删除SQL操作都会调用如下方法):
@Override
public int update(String statement, Object parameter) {
try {
// 标记 dirty ,表示执行过写操作
dirty = true;
// 获得 MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行更新操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
下面我们重点来看一下Executor的实现原理。
SimpleExecutor分析
代码如下:SimpleExecutor 继承了 BaseExecutor
public class SimpleExecutor extends BaseExecutor
继续进入BaseExecutor去查找query方法:
如下方法会涉及到一些缓存的逻辑,缓存会在后面分析,我们先看没有缓存的情况执行从数据库调用的代码。
调用query方法Executor会首先解析SQL语句会将#{},转换成JDBC识别的?,以及#{id}的值最终会返回一个封装好的BoundSql对象。
//此方法在SimpleExecutor的父类BaseExecutor中实现
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
//为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//.....
// 获得不到,则从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
//.....
return list;
}
BoundSql主要封装参数如下:
public class BoundSql {
/**
* SQL 语句
*/
private final String sql;
/**
* ParameterMapping 数组
*/
private final List<ParameterMapping> parameterMappings;
/**
* 参数对象
*/
private final Object parameterObject;
/**
* 附加的参数集合
*/
private final Map<String, Object> additionalParameters;
/**
* {@link #additionalParameters} 的 MetaObject 对象
*/
private final MetaObject metaParameters;
}
继续按照源码寻找queryFromDatabase最终会调用到doQuery方法,这是一个抽象方法很显然该方法的实现在SimpleExecutor类中
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
doQuery的实现代码如下,经历几个步骤:
- 该方法会创建一个StatementHandler对象
- prepareStatement得到具体的JDBC的Statement对象
- StatementHandler.query(statement,resultHandler) 执行查询数据库的操作,然后通过ResultHandler处理ResultSet结果集返回List集合。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 传入参数创建StatementHanlder对象来执行查询 真正的查询任务交给StatementHandler执行
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
// 初始化 StatementHandler 对象
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获得 Connection 对象
Connection connection = getConnection(statementLog);
// 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
从上述的代码中可以看到Executor的作用:
- 根据传递参数,完成SQL语句动态解析成JDBC识别的SQL动态语句,生成BoundSql对象提供给StatementHandler使用
- 为查询创建缓存,以提高性能(关于缓存会在下面进行讲解一级缓存和二级缓存)
- 创建JDBC的Statement链接对象,传递给StatementHandler对象,返回List查询结果
StatementHandler具体做了什么是不是很好奇,下面我们来看StatemetHandler的实现
StatementHandler
StatementHandler是一个接口,具体的StatementHandler的实现类对象是由Configuration类的newStatementHandler方法创建的,从上述的SimpleExecutor的代码中就可以看出
// 创建 StatementHandler 对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建 RoutingStatementHandler 对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 应用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
ok,就是他RoutingStatementHandler ,点进入:RoutingStatementHandler只是委托了创建StatementHandler会根据MappedStatment中的一个StatementType属性去创建具体的实现对象。路由的 StatementHandler 对象,根据 Statement 类型,转发到对应的 StatementHandler 实现类中。
/**
* 被委托的 StatementHandler 对象
*/
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据不同的类型,创建对应的 StatementHandler 实现类
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
从MappedStatement的代码中可以看到,默认使用的PREPARED类型
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
//.....
mappedStatement.statementType = StatementType.PREPARED;
}
}
ok,我们进入PreparedStatementHandler来查看StatementHandler的具体实现,继承了BaseStatementHandler,在上述的执行器代码中主要调用了两个方法prepare 、parameterize和 query.
prepare方法的具体实现:
该方法就是创建Statement对象,Statement对象通过instantiateStatement方法创建的
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 创建 Statement 对象
statement = instantiateStatement(connection);
// 设置超时时间
setStatementTimeout(statement, transactionTimeout);
// 设置 fetchSize
setFetchSize(statement);
return statement;
} catch (SQLException e) {
// 发生异常,进行关闭
closeStatement(statement);
throw e;
} catch (Exception e) {
// 发生异常,进行关闭
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
instantiateStatement()方法实现如下:
通过Connection创建prepareStatement,并将解析好的SQL语句传入
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
// 处理 Jdbc3KeyGenerator 的情况
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
在SimpleExecutor的代码中,调用prepare方法后紧接着调用parameterize 方法,实现如下:
该方法其实就是在创建完Statement后,需要给SQL设置参数,交给ParameterHandler 去设置
@Override
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
OK,创建完Statement对象,SQL执行的参数也设置完毕了,就可以去执行SQL语句了,我们再看query方法的实现,很熟悉的JDBC代码有没有
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 处理返回结果
return resultSetHandler.handleResultSets(ps);
}
ResultSetHandler对ResultSet的封装处理代码如下:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获得 ResultMap 数组
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount); // 校验
while (rsw != null && resultMapCount > resultSetCount) {
// 获得 ResultMap 对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理 ResultSet ,将结果添加到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
rsw = getNextResultSet(stmt);
// 清理
cleanUpAfterHandlingResultSet();
// resultSetCount ++
resultSetCount++;
}
// 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
String[] resultSets = mappedStatement.getResultSets();
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++;
}
}
// 如果是 multipleResults 单元素,则取首元素返回
return collapseSingleResultList(multipleResults);
}
综上所述,StatementHandler主要完成两个工作:
- 对于JDBC的PreparedStatement类型的对象,创建的过程中SQL语句字符串会包含若干个?占位符,通过调用parameterize(statement)方法对Statement进行设值
- 通过query方法,完成执行Statement并且将Statement对象返回的resultSet封装成list集合
OK,上述就是传统的通过传递statementId的方式的整体的代码逻辑分析完毕了,路漫漫其修远兮,Mapper接口代理的方式那么Mybatis是如何实现的呢?
Mapper代理方法源码剖析
其实Mapper代理实现非常简单,就是通过动态代理的方式实现的,如果你看过这篇文章就会知道mapper接口代理的方式如何实现的了
SqlSession sqlSession = sqlSessionFactory.openSession();
IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
List<Order> orderAndUser = mapper.findOrderAndUser();
for (Order order : orderAndUser) {
System.out.println(order);
}
上述代码中最终会调用SqlSession.getMapper方法来生成代理对象,进入源码中查看,调用了Configuration中的getMapper方法并且将代理接口和SqlSession的实例传递了过去
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// Configuration 类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry类
/**
* MapperProxyFactory 的映射
*
* KEY:Mapper 接口
*/
//这个类中维护一个HashMap存放MapperProxyFactory
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获得 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,则抛出 BindingException 异常
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);
}
}
最终会调用到了MapperProxyFactory类,那么有一个疑问knownMappers是什么时候将MapperProxyFactory实例添加到集合中去的呢?从上述的代码中并没有看到有任何代码将MapperProxyFactory实例添加到knownMappers集合中。
思考:一般使用mapper接口代理的方式我们会在配置文件中干什么呢?配置<package>标签扫描包
<!--引入映射配置文件-->
<mappers>
<!-- <mapper class="com.lagou.mapper.IOrderMapperr"></mapper>-->
<package name="com.lagou.mapper"/>
</mappers>
这时候应该就知道了,其实是在解析配置文件的过程中会创建MapperProxyFactory实例并且添加到knownMappers集合中。
我们回到解析Mapper配置文件的XMLConfigBuilder类代码,如下代码就是解析<mapper> 标签
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
// 如果是 package 标签,则扫描该包
if ("package".equals(child.getName())) {
// 获取 <package> 节点中的 name 属性
String mapperPackage = child.getStringAttribute("name");
// 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
configuration.addMappers(mapperPackage);
// 如果是 mapper 标签,
}
...
}
...
}
....
}
调用了configuration.addMappers方法,最终会调用到MapperRegistry的addMapper方法,knownMappers.put是不是就瞬间明白了,将一个MapperProxyFactory实例存储了。
public <T> void addMapper(Class<T> type) {
// 判断,必须是接口。
if (type.isInterface()) {
// 已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// 标记加载完成
loadCompleted = true;
} finally {
// 若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
OK,我们回到MapperProxyFactory的类,他就是实现mapper接口的动态代理实现。
//MapperProxyFactory类中的newInstance方法
public T newInstance(SqlSession sqlSession) {
// 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用了重载方法
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[]{mapperInterface}, mapperProxy);
}
方法的调用在MapperProxy实现了InvocationHandler接口,对象实现的核心代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 定义的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}
最终会调用到了MapperMethod对象的execute方法,本质上就是调用了SqlSession实例的 insert/update/select/delete方法后面的具体执行逻辑和传统方式的逻辑是一致的。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断mapper中的方法类型,最终调用的还是SqlSession中的方法
//获取映射文件中的对应的标签类型
switch (command.getType()) {
// 添加操作
case INSERT: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行 INSERT 操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//更新操作
case UPDATE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//删除操作
case DELETE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 执行查询,返回列表
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 执行查询,返回 Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 执行查询,返回 Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 执行查询,返回单个对象
} else {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 查询单条
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
在Mybatis与Spring整合中其实就是使用的Mapper接口代理方式,Spring将生成的代理对象存储到了IOC容器中,所以调用起来会非常方便直接从IOC容器中取出代理对象即可。