Mybatis源码系列文章
手写源码(了解源码整体流程及重要组件)
Mybatis源码解析(一):环境搭建
Mybatis源码解析(二):全局配置文件的解析
Mybatis源码解析(三):映射配置文件的解析
Mybatis源码解析(四):sql语句及#{}、${}的解析
Mybatis源码解析(五):SqlSession会话的创建
Mybatis源码解析(六):缓存执行器操作流程
Mybatis源码解析(六):查询数据库主流程
目录
- 前言
- 一、执行流程及组件
- 二、查询数据库解析入口
- 三、创建语句处理器
- 四、创建statement以及参数化设置
- 1、获取数据库连接
- 2、创建Statement对象
- 3、参数化处理
- 五、执行Sql
- 六、结果集处理
- 1、获取ResultSet
- 2、结果集转换pojo
- 总结
前言
- 上个文章讲到了查询入口,先查二级缓存,再查一级缓存,最后才会查询数据库
- 本篇文章围绕mybatis如何封装底层jdbc的查询操作
- 之后的源码对照的下图结合看,在源码中都能看到相同的代码
一、执行流程及组件
处理流程
- sqlSession调用方法,查询数据库操作会交给不同类型的执行器Executor
- 执行器会将任务交给不同类型的语句处理器StatementHandler(JDBC statement进行了封装)
- 入参和返回结果分别由ParameterHandler和ResultSetHandler处理器,而真正执行操作的是类型处理器TypeHandler
处理流程
- BaseStatementHandler:基础语句处理器(抽象类),它基本把语句处理器接口的核心部分都实现了,包括配置绑定、执行器绑定、映射器绑定、参数处理器构建、结果集处理器构建、语句超时设置、语句关闭等,并另外定义了新的方法 instantiateStatement 供不同子类实现以便获取不同类型的语句连接,子类可以普通执行 SQL 语句,也可以做预编译执行,还可以执行存储过程等
- SimpleStatementHandler:普通语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的处理,处理普通的不带动态参数运行的 SQL,即执行简单拼接的字符串语句,同时由于Statement的特性,SimpleStatementHandler 每次执行都需要编译 SQL (注意:我们知道 SQL 的执行是需要编译和解析的)
- PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的处理,相比上面的普通语句处理器,它支持可变参数 SQL 执行,由于PrepareStatement的特性,它会进行预编译,在缓存中一旦发现有预编译的命令,会直接解析执行,所以减少了再次编译环节,能够有效提高系统性能,并预防 SQL 注入攻击(所以是系统默认也是我们推荐的语句处理器)
- CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的处理,很明了,它是用来调用存储过程的,增加了存储过程的函数调用以及输出/输入参数的处理支持
- RoutingStatementHandler:路由语句处理器,直接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由功能,并把上面介绍到的三个语句处理器实例作为自身的委托对象而已,所以执行器在构建语句处理器时,都是直接 new 了 RoutingStatementHandler 实例
二、查询数据库解析入口
- 默认使用简单执行器,所以这里是SimpleExecutor的doQuery方法
- newStatementHandler:创建语句处理器StatementHandler
- prepareStatement:创建Statement对象及参数化设置(赋值?)
- handler.query:向数据库发出sql执行,返回结果集
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 1.获取配置实例
Configuration configuration = ms.getConfiguration();
// 2. new一个StatementHandler实例
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 3. 准备处理器,主要包括创建statement以及动态参数的设置
stmt = prepareStatement(handler, ms.getStatementLog());
// 4. 执行真正的数据库操作调用
return handler.query(stmt, resultHandler);
} finally {
// 5. 关闭statement
closeStatement(stmt);
}
}
三、创建语句处理器
进入newStatementHandler方法
- 前面说到这个路由语句处理器;无论创建哪个语句处理器,外面都要包一层路由处理器
- 插件会拦截不同的处理器,做自定义操作,后续章节单独讲
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建路由功能的StatementHandler,根据MappedStatement中的StatementType
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 插件机制:对核心对象进行拦截
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
进入RoutingStatementHandler构造方法
- 这里很明显看出路由处理器的作用,就是分发转换到不同的语句处理器
- StatementType是从MappedStatement中获取,则每个<select><update><insert><delete>标签都可以配置StatementType类型
- 通过设置不同类型,创建不同的语句处理器,默认是预编译语句处理器PreparedStatementHandler
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以及参数化设置
进入prepareStatement方法
- getConnection:获取数据库连接
- handler.prepare:创建Statement对象
- parameterize:参数化设置(给?赋值)
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 1. 获取代理后(增加日志功能)的Connection对象
Connection connection = getConnection(statementLog);
// 2. 创建Statement对象(可能是一个SimpleStatement,一个PreparedStatement或CallableStatement)
stmt = handler.prepare(connection, transaction.getTimeout());
// 3. 参数化处理
handler.parameterize(stmt);
// 4. 返回执行前最后准备好的Statement对象
return stmt;
}
1、获取数据库连接
- transaction:从事务对象中获取连接
- 日志部分,比较复杂,最后单独讲,这里就是创建一个连接对象的代理类,就是为了打印日志
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
进入transaction.getConnection方法
- dataSource:解析核心配置文件数据库连接、连接池等属性创建的数据源
- 创建SqlSession的openSession()方法,可以传入是否自动提交autoCommit和隔离级别level,这里算是派上用场了
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
2、创建Statement对象
通过RoutingStatementHandler handler的路由语句处理器调用prepare方法
- 这里RoutingStatementHandler内部的操作实际是交给了默认语句处理器PreparedStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
- 这个方法在语句处理器的基类BaseStatementHandler里,共用的,不同之处在instantiateStatement实例化方法里
- 后面就是设置下超时时间,大小属性
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
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实例化Statement方法
- 终于看到熟悉的东西了,connection.prepareStatement(sql),jdbc底层就是这么写的
- 其他的就是if判断,走的也就是重载方法
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
// 主键生成策略
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);
}
}
3、参数化处理
通过RoutingStatementHandler handler的路由语句处理器调用parameterize方法
- 与创建Statement对象一个套路,直接看PreparedStatementHandler的方法吧
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
- 这里就与最初说的流程对上了,具体的语句处理器是交给参数处理器parameterHandler来操作
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
- 参数处理器只有一个实现类DefaultParameterHandler
- ParameterMapping:#{}或${}中属性值、数据类型、Class对象、属性类型处理器
- 第三个if判断typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()):参数类型是否非自定义
- 自定义参数类型会通过工具类MetaObject反射获取对应属性值
- TypeHandler:回到文章最初,参数处理器最终还是交给类型处理器TypeHandler来操作
public class DefaultParameterHandler implements ParameterHandler {
// 持有typeHandler注册器
private final TypeHandlerRegistry typeHandlerRegistry;
// 持有MappedStatement实例,这是一个静态的xml的一个数据库操作节点的静态信息而已
private final MappedStatement mappedStatement;
// 当前实际执行前的参数对象
private final Object parameterObject;
// 动态语言被执行后的结果sql
private final BoundSql boundSql;
private final Configuration configuration;
// 构造函数
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 1. 获取boundSql中的参数映射信息列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 1.1. 遍历参数映射列表,这个列表信息就是我们xml文件中定义的某个查询语句的所有参数映射信息,注意这个List中的参数映射元素的顺序是和真实xml中sql的参数顺序对应的
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 1.2. 只有入参类型才会设置PreparedStatement
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 取出参数名,这里比如说是'id'
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 {
// 1.3. 这一步的工作就是从当前实际传入的参数中获取到指定key('id')的value值,比如是'15800000000'
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 2. 获取该参数对应的typeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 2.1. 获取该参数对应的jdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 3. 重点是调用每个参数对应的typeHandler的setParameter方法为该ps设置正确的参数值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
进入int类型处理器setParameter方法
- 这里又是熟悉的味道了,给?依次赋值
- 至此Statement创建及参数化处理完成,接下来回到开头《二、查询数据库解析入口》,最后一步执行sql
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
...
}
五、执行Sql
通过RoutingStatementHandler handler的路由语句处理器调用query方法
- RoutingStatementHandler只是分发作用,直接看PreparedStatementHandler的query方法吧
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
- 这里依然是属性的味道,ps.execute();如果是增删改,那就是ps.update()了
- sql执行完就是处理结果集了
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 结果集处理
return resultSetHandler.handleResultSets(ps);
}
六、结果集处理
- 结果集封装是交给结果处理器resultSetHandler来操作
- 参数处理器只有一个实现类DefaultResultSetHandler
- multipleResults:如组装结果为List<User>,那么List<List<User>> multipleResults
- getFirstResultSet:就是jdbc里面的获取ResutlSet,获取到之后.next()去遍历
- 不论<select>返回值属性是resultType还是resultMap,都会在xml解析时,封装成ResultMap对象
- ResultMap这里是集合,存储过程才有多结果集,才会getNextResultSet
- handleResultSet:将结果集封装到pojo
- collapseSingleResultList:multipleResults如果只有一个结果集(存储过程可能有多个),则get(0),返回则是我们需要的结果
@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;
// 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 这里是获取所有要映射的ResultMap(按照逗号分割出来的)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 要映射的ResultMap的数量
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 循环处理每个ResultMap,从第一个开始处理
while (rsw != null && resultMapCount > resultSetCount) {
// 得到结果映射信息(取出第一个结果集)
ResultMap resultMap = resultMaps.get(resultSetCount);
/*
* 根据映射规则对结果集进行pojo转化(最后放入multipleResults结果集中)
*/
handleResultSet(rsw, resultMap, multipleResults, null);
// 处理下个结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 对应<select>标签的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++;
}
}
// 如果只有一个结果集合,则直接从多结果集中取出第一个
return collapseSingleResultList(multipleResults);
}
1、获取ResultSet
- 通过Statement执行sql后获取ResultSet
- ResultSetWrapper:ResultSet结果集装饰类
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
2、结果集转换pojo
- handleRowValues:结果集转换pojo内容
- 最终将结果放入multipleResults集合
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
// 实例化DefaultResultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 对结果集进行映射,转换的结果存入defaultResultHandler
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// List<List<User>>
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 存储过程相关
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
进入handleRowValues方法
- 嵌套结果集:返回结果的对象属性中有其他对象,一般就是简单结果映射
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有内置嵌套的结果映射
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
// 嵌套结果映射
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 简单结果映射
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
- skipRows:根据分页对象,截取对应的数据
- getRowValue:将查询结果封装到POJO中
- storeObject:将结果添加到DefaultResultHandler,最终为了添加到multipleResults里
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 获取结果集信息
ResultSet resultSet = rsw.getResultSet();
// (1)根据分页信息,提取相应数据
skipRows(resultSet, rowBounds);
/*
处理和赋值多条记录
*/
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 将查询结果封装到POJO中
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 保存映射结果
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
进入getRowValue
- createResultObject:根据resultType全限定类名反射获取对象(User空属性对象)
- metaObject元对象:通过反射为原始对象(user)的属性赋值
- hasTypeHandlerForResultObject:结果集对象的属性是否都有对应的TypeHandlle类型处理器
- applyAutomaticMappings:返回对象为ResultType类型
- applyPropertyMappings:返回对象为ResutlMap类型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 延迟加载的映射信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 根据resultType的值创建要映射的PO类对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 获取MetaObject对象,为后面赋值做准备
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否应用自动映射,也就是通过resultType进行映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 根据columnName和type属性名映射赋值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 根据我们配置ResultMap的column和property映射赋值
// 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
进入applyAutomaticMappings
- UnMappedColumnAutoMapping对象由数据库字段、实体类字段、字段类型处理器组成
- 遍历字段 (类型处理器、resultSet、数据库字段 = 属性值)
- metaObject.setValue:通过反射为原始对象user的属性赋值
- 一路返回,结果添加到DefaultResultHandler,最终为了添加到multipleResults里
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
总结
- Mybatis查询数据库的流程就是jdbc底层流程的封装,jdbc每一步都会能在mybatis封装的对象中找到对应的步骤
- mybatis为了扩展性强,几乎所有流程组件都是接口,然后不同的实现类,做不同的操作(先创建xxx处理器,再通过xxx处理器创建x1、x2、x3)
- StatementHandler:先根据解析的xml配置文件创建对应的语句处理器
- 语句处理器+数据库连接创建Statement
- ParameterHandler:参数化设置,给sql中的?赋值
- 执行sql,与jdbc代码一样
- ResultSetHandler:封装结果集,将结果集转换为pojo