今天继续来看下MapperMethod.execute方法后续通过Executor都做了些什么?今天主要梳理整个流程,不会过的很细,后续有需要会详细过一些重要类的实现,比如MetaObject。
MapperMethod.execute实现
首先看下整个方法,一些不重要的部分我直接省去了:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} 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());
}
、、、、、、、、、、、、、、、、、省略
}
return result;
}
可以看出,这里会根据我们前面构造的SqlCommand的type去进行分类执行,这个type是如何得到的呢?根据xml里面的标签或者mapper接口里面的注解,就是SELECT这个type,注意FLUSH对应的是@Flush注解,用来刷新用的。
很容易看出select的实现会比其他场景复杂的多,主要是有返回值的解析和数据组装。
INSERT的调用流程
我就以第一个Insert来描述一下整个流程。首先拿到Param,这个在上一张说过整个参数的详细获取流程和主要的实现类,这里就不再赘述。
然后是调用defaultSqlsession的insert方法,很有意思的是,insert其实会调用update方法,其实也很好理解所有的insert,update,delete其实在数据库看来都是update。
然后一直往下会到BaseExecutor的doUpadte方法,这个是一个抽象方法,会由它的子类实现,这三个子类对应的三个type,simple,resued,batch,你设置的哪个就走到哪个里面,如果没有设置默认是走的simleExecutor。
这里会调用BaseStatementHandler的构造函数,其他不重要,比较重要的是会去生成BoundSql,它里面会持有所有xml里面的sql语句和参数,属性如下:
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
这个是一个很重要的类,如果更改这个类,你可以做很多事,比如有的基于mybatis的分页插件实现就是在修改boundSql里面的sql,在后面加上你设定好的page和size就行。
然后进入simleExecutor的prepareStatement方法,这个方法做的事情也比较简单:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
获取连接,生成statement,然后调用prepareStatement的set方法进行赋值,这个和我最开始基础知识里面说的jdbc底层操作是一样的。
最终实现是在DefaultParameterHandler的setParameters方法:
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
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 = 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);
}
}
}
}
}
最后的typeHandler.setParameter(ps, i + 1, value, jdbcType);
就是进行赋值,只是这里面会根据你定义的typeHandler做相应的类型处理而已。
设置完值之后会调用prepareStatement的execute方法,具体实现是在PreparedStatementHandler的update方法:
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
总结
今天简单梳理了下Mybatis在动态代理的方法调用之后是如何进行对数据库的操作的,底层是交给executor去做的,然后会给具体的StatementHandler操作,当然里面还有很多细节,比如如何对xml数据赋值,类似#{a.b.c}这种,还有在哪里将sql的#{}参数替换为?的,有兴趣的可以自己看看,或者留言,后续会更新该部分内容。