mybatis执行流程_MyBatis源码解析之一 MyBatis运行流程详解

0ffed9274dc83530980a8d59ac05c8da.gif

想了很久MyBatis源码解析的第一篇文章写什么,后面还是决定先介绍一下MyBatis源码的运行过程,让小伙伴们对MyBatis的工作流程有个简单的了解,方便后面阅读源码.

我们先通过一个图来看看MyBatis的执行流程e28602910db06c8c4044305682d4b11d.png

上面的流程图,我们看到MyBatis的流程,下面我们通过代码来具体看看执行流程,下面是一段测试代码

public class GetMapperWay {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder创建
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlMapper.openSession();
try {
//第二种方式
UserDao userDao = session.getMapper(UserDao.class);
User user1 = userDao.getUser(1);
System.out.println(user1.getName());
} finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

第一步,通过SqlSessionFactoryBuilder创建一个SqlSessionFactory

进入SqlSessionFactoryBuilder的build()方法,可以看到该方法委托XMLConfigBuilder类完成对mybatis-config.xml的解析,并将解析到的值封装到Configuration这个对象中

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//解析配置文件的关键逻辑都委托给XMLConfigBuilder类来完成
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.
}
}
/**
* 开始解析mybatis配置文件,将解析结果返回到Configuration对象上
* @return
*/
public Configuration parse() {
//判断配置文件是否被解析过,没有解析过的才开始解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

/**
* 解析mybatis-config.xml的各个标签值
* @param root 配置XML的内容
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//读取数据环境相关配置
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析XML配置文件中mappers节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

调用带有Configuration参数的方法返回了SqlSessionFactory的默认实现类DefaultSqlSessionFactory

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

第二步,通过SqlSessionFactory创建SqlSession

 public SqlSession openSession() {
// 这里使用默认的执行器类型(默认是SIMPLE),默认隔离级别,非自动提交 委托给openSessionFromDataSource方法
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* openSession方法最终都会调用本方法
* @param execType
* @param level
* @param autoCommit
* @return
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关的Environment配置(包含了数据源和事务的配置)信息
final Environment environment = configuration.getEnvironment();
// 获取事务管理器, 支持从数据源或者直接获取
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从数据源创建一个事务, 同样,数据源必须配置, mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,
// 它是mybatis自带实现的一个同步、线程安全的数据库连接池 一般在生产中,我们会使用dbcp或者druid连接池
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建一个Executor来执行SQL语句,Executor完成了对Statement的封装
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拿到Mapper对象的代理

 @Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
 public  T getMapper(Class type, SqlSession sqlSession) {
//解析Mybatis-config.xml的时候,在解析标签mapper就是用configuration对象的mapperRegistry存放数据
return mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {
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);
}
}
protected T newInstance(MapperProxy mapperProxy) {
//动态代理我们写的dao接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

第四步,通过MapperProxy的invoke调用真正的接口

/**
* 每一个MapperProxy对应一个Dao接口,在执行时会触发此方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 实际交给MapperMethod自己去管理
return mapperMethod.execute(sqlSession, args);
}

第五步,MapperMethod执行execute

这里以sqlSession.selectOne(command.getName(), param)为例,在DefaultSqlSession类中,我们看到查询单个对象的操作最终是查询集合的实现之一

public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从MappedStatement容器中获取SQL相关信息
MappedStatement ms = configuration.getMappedStatement(statement);
//CRUD的实际处理又交还给了Executor
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执行SQL

先在BaseExecutor处理部分逻辑,具体交个子类去实现

public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 委托给重载的query方法完成具体逻辑
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// doQuery是个抽象方法,每个具体的执行器都要自己去实现
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 把从数据的查询结果放到缓存中去
localCache.putObject(key, list);
// 如果是存储过程类型,则把查询参数放到本地出参缓存中, 所以第一次一定为空
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

具体的交给子类SimpleExecutor去实现,想想看到这里,小伙伴们应该很熟悉了,如果你还不清晰,那就去复习一下JDBC吧

@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//此处实例化一个StatementHandler,包含了结果处理器、参数处理器、执行器等等,主要有三种类型的语句处理器UNPREPARE、PREPARE、CALLABLE。默认是PREPARE类型,
// 通过mapper语句上的statementType属性进行设置,一般除了存储过程外都应该设置
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//此处是真正开始跟JDBC打交道的地方
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封装了Statement, 让 StatementHandler 去处理
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

第七步,处理返回结果集

DefaultResultSetHandler类中处理查询返回的结果

public List handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List multipleResults = new ArrayList();
int resultSetCount = 0;
// 返回jdbc ResultSet的包装形式,主要是将java.sql.ResultSetMetaData做了Facade模式,便于使用
ResultSetWrapper rsw = getFirstResultSet(stmt);
List resultMaps = mappedStatement.getResultMaps();
// 绝大部分情况下一个查询只有一个ResultMap, 除非多结果集查询
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
//确保至少执行一次
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 根据resultMap的定义将resultset打包到应用端的multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
// 循环直到处理完所有的结果集,一般情况下,一个execute只会返回一个结果集,除非语句比如存储过程返回多个resultSet
rsw = getNextResultSet(stmt);
// 清空嵌套结果集
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 处理关联关系或集合等情况
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
// 如果一个映射语句的resultSet数量比jdbc resultset多的话,多的部分就是嵌套结果集
while (rsw != null && resultSetCount < resultSets.length) {
// nextResultMaps初始化的时候为空,它是在处理主查询每行记录的时候写进去的,所以此时就可以得到主记录是哪个属性
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// 得到子结果集的resultMap之后,就可以进行填充了
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}

以下是作者为MyBatis添加中文注释的git地址,目前还在不断完善中,帮忙点个star,有兴趣的也可以fork到自己的码云下添加一些自己的想法!

git地址:https://gitee.com/Somta/Mybatis

97025cc2f1aa120d113dd8d6bbc8985b.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值