首先要了解MyBatis如何动态代理接口的方法的,和JDBC基本编程
参考:MyBatis源码笔记(四) – mapper动态代理
执行方法主要封装在MapperMethod
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
...
}
MapperMethod在构造方法中实例化了两个成员变量。
SqlCommand是一个内部类
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//完整接口名+方法名
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//查询是否有以该statementName作为Id的MappedStatement
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
//如果不是这个mapper接口的方法,再去查父类
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {//没有就抛异常
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
name = ms.getId();//目标MappedStatement的id
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
...
}
SqlCommand主要是拿到接口方法对应的MappedStatement的id,和MappedStatement的id的SQL类型(增删查改)
//方法签名,静态内部类
public static class MethodSignature {
...省略成员变量
public MethodSignature(Configuration configuration, Method method) {
//返回值类型
this.returnType = method.getReturnType();
//是否没返回值
this.returnsVoid = void.class.equals(this.returnType);
//是否返回多个结果()
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
//如果返回是个Map类型,则对@MapKey进行处理(如果有的话)
this.mapKey = getMapKey(method);
//是否返回Map
this.returnsMap = (this.mapKey != null);
//是否有@Param注解
this.hasNamedParameters = hasNamedParams(method);
//以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
//记下RowBounds是第几个参数
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//记下ResultHandler是第几个参数
this.resultHandlerIndex = getUniqueParamIndex(method, RowBounds.class);
//SortedMap类型的params只包含除RowBounds、RowBounds以外的参数,键为参数位置下标,值为数字或@Param注解的值
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
...
}
MethodSignature主要对接口方法的返回值、参数做一些处理和记录,以供调用时使用。
介绍完两个内部类,接着看MapperMethod的execute方法,它是在接口代理类真正开始执行接口方法时调用。
//执行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
if (SqlCommandType.INSERT == command.getType()) {
//1.执行insert语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
//2.执行update语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
//3.执行delete语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//4.执行select语句
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw...//没有相应的执行类型
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw...//返回类型不对
}
return result;
}
上面可以看到对四种不同的SQL类型进行了不同的处理,本篇只分析增删改
1.执行insert语句
首先调用了MethodSignature成员的convertArgsToSqlCommandParam方法拿到参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
//如果没参数
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//如果只有一个参数,取到不是RowBounds、ResultHandler类型的参数返回
return args[params.keySet().iterator().next().intValue()];
} else {
//否则,返回一个ParamMap,修改参数名,参数名就是其位置
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//(1).先加0,1,2...(或是以@Param注解起名)参数
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
//(2).再加param1,param2...参数
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
```j
>这方法返回的参数是不包括RowBound、ResultHandler类型的,可以看到若是多个参数的话,它可以以#{0}、#{1}这样取,或以#{param1}、#{param2}形式取
拿到参数后,接着就是调DefaultSqlSession中的insert方法,参数是对应MappedStatement的id,和刚取到的参数
```java
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
//每次要更新之前,dirty标志设为true
dirty = true;
//根据id拿到相应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//转而用执行器来update结果
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
//参数若是Collection型,做collection标记
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
//参数若是List型,做list标记
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
//参数若是数组型,,做array标记
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
//参数若不是集合型,直接返回原来值
return object;
}
上面可以看到,先把dirty标志设为true,然后通过configuration拿到相应的MappedStatement,接着对parameter进行集合判断处理,最终是调用了executor的update方法,而executor默认是SimpleExecutor实例。
SimpleExecutor没有update方法,在其父类BaseExecutor类中
public int update(MappedStatement ms, Object parameter) throws SQLException {
...
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//先清局部缓存,再更新,如何更新交由子类,模板方法模式
clearLocalCache();
return doUpdate(ms, parameter);
}
public void clearLocalCache() {
if (!closed) {
//实际存在HashMap中
localCache.clear();
localOutputParameterCache.clear();
}
}
上面先是清局部缓存,再更新,更新方法在SimpleExecutor有实现
SimpleExecutor类
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//1.1新建一个StatementHandler
//这里看到ResultHandler、BoundSql传入的都是null
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//1.2准备语句
stmt = prepareStatement(handler, ms.getStatementLog());
//1.3执行
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
doUpdate方法显示创建了一个StatementHandler实例,然后对Statement进行处理,接着调用StatementHandler的update方法真正开始执行。
1.1新建一个StatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建路由选择语句处理器
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//插件在这里插入
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据语句类型,委派到不同的语句处理器(STATEMENT|PREPARED|CALLABLE)
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());
}
}
newStatementHandler主要是创建一个RoutingStatementHandler实例,而RoutingStatementHandler是继承StatementHandler的,它的构造方法也说明了它不是真的做事的,它会根据不同的SQL类型代理给不同的StatementHandler,一般用的是预处理语句,所以接下来会分析PreparedStatementHandler。另外上面也看到插件可以包装StatementHandler,后续的文章会分析插件实现源码。
1.2准备语句
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//1.2.1获取Connection
Connection connection = getConnection(statementLog);
//1.2.2调用StatementHandler.prepare
stmt = handler.prepare(connection);
//1.2.3调用StatementHandler.parameterize
handler.parameterize(stmt);
return stmt;
}
1.2.1获取Connection
protected Connection getConnection(Log statementLog) throws SQLException {
//transaction分JdbcTransaction和ManagedTransaction
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
//如果需要打印Connection的日志,返回一个ConnectionLogger(代理模式, AOP思想)
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
按照JDBC的套路:调用底层驱动拿到Connection,然后从Connection中实例化一个Statement,然后在调用statement的方法进行真正查询数据库。MyBatis判断如果是调试模式的话,会用ConnectionLogger动态代理Connection类以便于打印日志
接着看handler.prepare(connection),基于PreparedStatementHandler分析
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//实例化Statement
statement = instantiateStatement(connection);
//设置超时
setStatementTimeout(statement);
//设置读取条数
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);
}
}
实际是通过connection.prepareStatement创建一个Statement,接着对这个Statement进行一个设置
1.2.3调用StatementHandler.parameterize
public void parameterize(Statement statement) throws SQLException {
//调用ParameterHandler.setParameters
parameterHandler.setParameters((PreparedStatement) statement);
}
parameterHandler是个成员变量,以PreparedStatementHandler为例,如下:
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
...
//生成parameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
...
}
所以parameterHandler最后是调动configuration的newParameterHandler方法创建出来的
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
//创建ParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//插件在这里插入
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
//返回默认的参数处理器
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
最后得到的是DefaultParameterHandler实例
好了现在可以去看DefaultParameterHandler的setParameters方法了
public void setParameters(PreparedStatement ps) throws SQLException {
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) {
//如果不是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) {
//若参数为null,直接设null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//若参数有相应的TypeHandler,直接设object
value = parameterObject;
} else {
//除此以外,MetaObject.getValue反射取得参数指定属性值设进去
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
//不同类型的set方法不同,所以委派给子类的setParameter方法
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
上面对parameterMappings进行循环,并拿到相应的值,最后调用typeHandler.setParameter对statement进行设值
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
//特殊情况,设置NULL
if (parameter == null) {
if (jdbcType == null) {
//如果没设置jdbcType,报错啦
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
//设成NULL
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
"Cause: " + e, e);
}
} else {
//非NULL情况,怎么设还得交给不同的子类完成, setNonNullParameter是一个抽象方法
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
setNonNullParameter交由不同的子类实现,例如Byte、Date等类型
1.3真正执行
还是以PreparedStatementHandler为例
public int update(Statement statement) throws SQLException {
//调用PreparedStatement.execute和PreparedStatement.getUpdateCount
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;//返回影响行数
}
回顾一下前面
result = rowCountResult(sqlSession.insert(command.getName(), param));
插入返回的结果,调用rowCountResult方法进行处理
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
//如果返回值是大int或小int
result = Integer.valueOf(rowCount);
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
//如果返回值是大long或小long
result = Long.valueOf(rowCount);
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
//如果返回值是大boolean或小boolean
result = Boolean.valueOf(rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
可以看到,插入的返回结果只允许是空值、数字、布尔值。
2.执行update语句
result = rowCountResult(sqlSession.update(command.getName(), param));
这里直接调用了sqlSession的update方法,上面已分析过
3.执行delete语句
...
result = rowCountResult(sqlSession.delete(command.getName(), param));
...
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
同样最终还是调用了sqlSession的update方法