MyBatis运行
操作数据库
Executor
前面分析了MyBatis从mapper.xml到sql解析一直到生成Mapper的代理实例,了解了MyBatis整个工作流程。接下来我们来看MyBatis与数据库的交互。
先来看一段代码:
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这是DefaultSqlSession中执行查询的源码,此处我们可以看到在执行查询的时候是通过executor这个对象去操作的,在DefaultSqlSession中所有与数据库的操作都是通过这个executor对象操作的。
同时在mybatis-config.xml配置文件中也可以指定执行sql默认所用的Executor。
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
因此我们大概可以知道Executor就是MyBatis针对数据库操作的一层封装,或者说是对JDBC的Statement的封装。 每个SqlSession对应一个Executor。
从配置我们可以看出来,Executor有多种类型,它们分别是
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
对应的的Executor实现类:
Executor类型
Executor从配置上面来看,可以配置为SIMPLE, REUSE, BATC,分别对应了SimpleExecutor,ReuseExecutor,BatchExecutor。
SimpleExecutor:简单执行器,也是MyBatis的默认执行器,每次执行一次数据操作(update,select)就开启一个Statement,用完就关闭。
ReuseExecutor:可重用的执行器,这里重用的是Statement,它会在一个Session作用域中,每次执行sql的时候会判断该sql是否已经执行过,每次执行过会将Statement进行缓存起来(一个Map),下次执行的时候直接重用上次的Statement。这种适用于开启一个会话要执行多次数据库操作,并且其中会带有重复的sql情况下使用。
BatchExecutor:批处理执行器,从名字就可以分辨出来,它会将多个SQL(一个会话),一次性执行。
以上三种Executor就是我们通常使用和可配置的Executor。
还有个特例独行的CachingExecutor。
CachingExecutor,自身不会有Executor的逻辑,它可以说是实际Executor的静态代理类,在原本的Executor基础上加了缓存功能,主要是针对查询,在执行sql前会查询一级缓存(Session级别的缓存)是否存在结果,如果存在则直接返回,否则就委派实际的Executor去执行,所委派的Executor就是上面的三种。
public class CachingExecutor implements Executor { private final Executor delegate; }
Executor的创建
Executor的创建是由SqlSessionFactory调用Configuration#newExecutor创建的。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
配置cacheEnabled默认为true,因此没有特殊配置的情况下都是开启了一级缓存了的,使用的是CachingExecutor,实际执行还是按照配置的ExecutorType创建的。
executor = (Executor) interceptorChain.pluginAll(executor); 拦截器,后面说。
StatementHandler
前面介绍了MyBatis中,真正与数据库交互的是通过Executor进行交互的,那么MyBatis是如何封装Executor,怎么用它操作数据库的呢?
接下来我们来看一段,Executor操作数据库的源码
org.apache.ibatis.executor.SimpleExecutor#doQuery
@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();
// 创建StatementHandler,StatementHandler用于创建JDBC的Statement,以及执行查询和返回
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备:java.sql.Statement,如对Statement设置参数等,设置超时参数等信息
stmt = prepareStatement(handler, ms.getStatementLog());
// 最后执行查询,返回其结果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上面这一段是Executor查询的代码,update代码也类似:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
通过上面代码分析,可以总结一下Executor操作数据库的步骤:
- 创建StatementHandler
- 用创建的StatementHandler,创建JDBC的java.sql.Statement,并设置参数
- 执行JDBC的Statement,并返回其结果
由上分析,可以看出StatementHandler是一个非常重要的类。
StatementHandler的作用:StatementHandler主要用于创建及设置JDBC中java.sql.Statement的参数和执行Statement,并封装返回结果。
如果忘记了JDBC的Statement,看一段这个原生的JDBC代码,就能知道它主要在干啥了:
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root");
//依据姓名查找学生信息
String studentname="lili";
String sql = "select * from student where studentname=?";
//创建PrepareStatement
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, studentname);
//执行SQL
resultSet = preparedStatement.executeQuery();
//处理结果
while (resultSet.next()) {
Student student = new Student();
int id = resultSet.getInt("studentid");
String name = resultSet.getString("studentname");
student.setStudentID(id);
student.setStudentName(name);
System.out.println(student);
}
} catch (Exception e) {
e.printStackTrace();
//关闭资源
} finally {
}
上面JDBC代码中的PreparedStatement就是java.sql.Statement的实现类。
StatementHandler接口:
public interface StatementHandler {
// 准备Statement,也就是创建Statement,并设置其超时参数
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 对Statement设置sql中的占位参数
void parameterize(Statement statement)
throws SQLException;
// 批量操作
void batch(Statement statement)
throws SQLException;
// update操作,删除也是它
int update(Statement statement)
throws SQLException;
// 查询...
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
StatementHandler体系:
它的继承体系跟Executor类似,真正干活儿的就是BaseStatementHandler的实现类,RoutingStatementHandler是一个静态代理类,内部跟CachingExecutor一样,有个delegate属性,是BaseStatementHandler三个实现类其中一个。
着重介绍一下,这个RoutingStatement的作用,在Executor体系中CachingExecutor是一个静态代理Executor在Executor实现的基础上增加了缓存功能。这里的RoutingStatement功能类似,但是它实现的不是缓存,它实现的是一个路由选择功能:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
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());
}
}
它会根据Statement类型,创建对应真正去操作这个Statement的Handler。
那么接下来你有可能要问了,这三种实现的的StatementHandler有什么区别呢?
SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句,也就是可以直接执行的sql语句。PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句,需要参数设置等操作的sql语句,是一个预编译的sql。CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。这个StatementType是在解析mapper.xml生成MappedStatement的时候就决定了。
StatementHandler源码
接下来我们分析一下StatementHandler的源码,从而推出另外两个重要组件ParameterHandler、ResultSetHandler。
先来看StatementHandler的创建:
// org.apache.ibatis.executor.SimpleExecutor#doQuery
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建RoutingStatementHandler,RoutingStatement的创建逻辑在上面有介绍
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 拦截器,后面会介绍
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看到,实际对外工作的跟Executor类似,是这个代理对象RoutingStatementHandler。
(StatementHandler) interceptorChain.pluginAll(statementHandler);这里又出现了类似创建Executor的代码,还是拦截器功能实现,后面会详细讲。
StatementHandler准备:
// org.apache.ibatis.executor.SimpleExecutor#doQuery
stmt = prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数
handler.parameterize(stmt);
return stmt;
}
这一步,创建了Statement,并且对其参数进行了设置
最后执行返回:
// org.apache.ibatis.executor.SimpleExecutor#doQuery
return handler.query(stmt, resultHandler);
步骤很简单,就分了三步,现在我们来考虑一下这三步结合我们写原生JDBC风封装,比较难的点在哪儿?
首先,肯定不是创建一个Statement,毕竟JDBC提供了API一下就创建好了,主要难点在:参数设置和返回结果封装。
那么MyBatis是如何做的呢?
在StatementHandler的功能实现类有个功能父类就是BaseStatementHandler,里面有两个重要属性:
就是ResultSetHandler和ParameterHandler,一个对数据库操作结果进行封装成返回结果,一个是对数据库操作sql参数设置。
ParameterHandler
ParameterHandler没有上面的Executor和StatementHandler复杂
public interface ParameterHandler {
// 获取参数对象
Object getParameterObject();
// 针对PreparedStatement设置参数对象
void setParameters(PreparedStatement ps) throws SQLException;
}
它的功能比较单一,就是给PreparedStatement设置参数,类似于JDBC的:
//创建PrepareStatement
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, studentname);
并且ParameterHandler也只有一个实现类
它的创建跟Executor和StatementHandler一样,都是由Configuration对象创建
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// .....
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// ....
}
// org.apache.ibatis.session.Configuration#newParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
mappedStatement.getLang():获取到的是LanguageDriver实例,在前面介绍解析MappedStatement的时候有说道对它的创建。
有注意到,parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);又是一段跟其它组件类似的,拦截器,照旧,后面会说。
另外着重说一下ParameterHandler的核心功能,设置参数
DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 遍历ParameterMapping,这个ParameterMapping集合的生成在前面也有说到
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.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) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取到TypeHandler,针对不同类型设置方式不同,TypeHandler有很多实现类
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
设置参数主要步骤:
- 遍历 BoundSql的parameterMappings,对每个parameter设置属性。并从传来的参数解析出值。
解析BoundSql中的parameterMappings的源码在:SqlSourceBuilder#parse => 对ParameterMappingTokenHandler; - 针对每个ParameterMapping获取到对应的TypeHandler
- 调用TypeHandler设置参数值
ResultSetHandler
前面分析了ParameterHandler,对sql参数设置,接下来我们分析对数据库操作结果进行封装,将会使用到ResultSetHandler。
public interface ResultSetHandler {
// 处理结果集,用得最多的
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 批量处理结果集
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理存储过程结果
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
默认,也仅且一个实现类就是DefaultResultSetHandler。
跟其它的一样,创建ResultSetHandler也是由Configuration完成的
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// .....
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
// ....
}
// org.apache.ibatis.session.Configuration#newResultSetHandler
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
创建逻辑比较简单,我们又看到了熟悉的身影 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 拦截器,还是放到后面统一说。
接下来我们看它的核心方法:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获取第一个结果集,封装成ResultSetWrapper
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 持续遍历结果,并处理:java.sql.ResultSet。将其封装到multipleResults集合中
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
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++;
}
}
return collapseSingleResultList(multipleResults);
}
在对返回结果封装入口代码就是:handleResultSet(rsw, resultMap, multipleResults, null);
ResultSetHandler是通过反射创建的返回对象,源码是DefaultResultSetHandler#createResultObject(....)。
MyBatis操作数据库总结
上面这个图,描述了Executor在操作数据库的时候和JDBC操作数据库的对应关系步骤,我们可以看到Executor实际底层也是使用JDBC,只不过在操作Statement以及参数设置,查询返回等关键步骤封装成了组件,也就是:StatementHandler、ParameterHandler、ResultSetHandler,网上有把它们与Executor组件统称为”MyBatis四大组件“,“SqlSession四大对象“....。
Executor:负责发起操作数据库sql请求,初始化其它三大组件。
StatementHandler:语句处理器,负责和JDBC交互,包括创建JDBC的Statement,处理prepare、执行语句,调用ParameterHandler设置参数,调用ResultSetHandler封装返回结果。
ParameterHandler:参数处理器,负责处理预编译sql的入参,依赖ParameterMapping。
ResultSetHandler:结果处理器,负责将数据库操作结果映射成Java返回结果。
我们在追溯源码的时候,可以看到这四个组件,都在创建的时候,执行了一个类似于这样的语句:interceptorChain.pluginAll(resultSetHandler); 这是MyBatis提供的拦截器,方便编码人员能够随时干预数据库执行过程中的步骤,如参数改写,sql改写,返回结果再封装等操作,MyBatis并没有向Spring框架一样,提供回调接口的方式来进行扩展而是采用的拦截器,这种方式,扩展性更强一点,也更灵活,但是对新手也不太友好,因为需要了解到四大组件的功能具体API功能才能很好应用。
](blog.51cto.com/u_12393361/…)
MyBatis执行流程总结
至此就分析完了整个MyBatis运行阶段,从sql生成到sql执行的整体流程,接下来统一做个小总结
- 在启动的时候通过获取要扫描的mapper.xml或者注解,进行将sql片段解析为SqlNode对象,生成MappedStatement对象,放入Configuration中。
- 在运行过程中获取到Mapper接口实例,通过SqlSession#getMapper方法为入口,创建Mapper接口的代理实例,代理对象方法的执行者为MapperProxy。
- 调用Mapper接口实例的时候,实际执行逻辑是MapperProxy的invoke方法,该方法会通过启动时的MappedStatement以及执行过程中Mapper接口方法来进行判断当前执行方法是什么样的类型,从而调用SqlSession的方法,执行sql语句。
- SqlSession执行sql前,会通过第一步解析好的MappedStatement,从而根据参数解析出预编译sql,以及参数信息。
- 通过Executor执行sql语句。