MyBatis源码笔记(七) -- insert、update、delete语句的执行

首先要了解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方法

猜你喜欢

转载自blog.csdn.net/seasonLai/article/details/82905294