Mybatis源码(四)mybatis的执行流程之增删改

上篇博客我们介绍了mybatis的执行流程中查询的操作,同时介绍了两种SQL语句。但是增删改没有讲,这篇博客我们就介绍一下mybatis中的增删改的执行流程。首先我们书写以下的测试的代码,具体的代码如下:

public class TestInsertOrUpdateOrDelete {
  public static void main(String[] args) throws IOException {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    System.out.println(mapper.insertUser("haha","123"));
    System.out.println(mapper.updateUser(new Admin(177,"xixi","321")));
    System.out.println(mapper.deleteUser(177));
    sqlSession.close();
  }
}
public class Admin {
  private int id;
  private String username;
  private String password;
  public Admin() {
  }
  public Admin(int id, String username, String password) {
    this.id = id;
    this.username = username;
    this.password = password;
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getPassword() {
    return password;
  }
  public void setPassword(String password) {
    this.password = password;
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.DemoMapper">
  <insert id="insertUser" parameterType="string">
    insert into admin values (#{username},#{password});
  </insert>
  <update id="updateUser" parameterType="com.Admin">
    update admin set username = #{username},password = #{password} where id = #{id};
  </update>
  <delete id="deleteUser" parameterType="int">
    delete * from admin where id = #{id}
  </delete>
</mapper>
public interface DemoMapper {
  int insertUser(@Param("username") String username, @Param("password") String password);
  int updateUser(Admin admin);
  int deleteUser(int id);
}

上面的测试基本上涵盖了所有的情况,以及注解的情况。这儿添加注解是因为我们之前没有使用过注解,所以这儿添加了注解,查看一下注解的情况下参数的包装。让我们随着代码的执行的流程一步步跟下去。具体的代码如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        //你是不是调用的Object默认的方法
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        //对于默认方法的处理
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
}

这儿由于前面查询的时候已经介绍了,所以这儿我就不做过多的介绍,直接看mapperMethod.execute(sqlSession, args);方法,具体的代码如下:

public class MapperMethod {
	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());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
}

这儿这个switch选择,不用说,肯定是执行insert中方法,至于这个Type在什么时候封装,我们在前一篇博客中已经介绍过了。这个时候会执行method.convertArgsToSqlCommandParam(args);方法,这个方法是直接对传过来的参数进行封装。我们直接跟进去看看,具体的代码如下:

public static class SqlCommand {
	public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
}
public class ParamNameResolver {
  public Object getNamedParams(Object[] args) {
      final int paramCount = names.size();
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        return args[names.firstKey()];
      } else {
        final Map<String, Object> param = new ParamMap<>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
          //names : arg0,arg1
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param
          if (!names.containsValue(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }
}

你会发现这儿的代码和前面的代码是一样的,这个时候由于我们给指定的参数的中加了@param注解,所以这儿获取到的Map的内容如下:

{key:username value:haha}
{key:password value:123}
{key:param1 value:haha}
{key:param2 value:123}

这个执行参数的转换成Map,然后执行rowCountResult(sqlSession.insert(command.getName(), param));方法。这个时候会先执行sqlSession.insert(command.getName(), param)方法,让我们继续跟进去看看对应的代码,具体的代码如下:

public class DefaultSqlSession implements SqlSession {	
	@Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
}

上面的代码会执行update的方法。我们继续跟进,具体的代码如下:

public class DefaultSqlSession implements SqlSession {		
	@Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

上面代码会执行executor.update(ms, wrapCollection(parameter));方法,执行这个方法之前,会执行 wrapCollection(parameter)方法,具体的代码如下:

private Object wrapCollection(final Object object) {
  if (object instanceof Collection) {
  StrictMap<Object> map = new StrictMap<>();
  map.put("collection", object);
  if (object instanceof List) {
    map.put("list", object);
  }
    return map;
  } else if (object != null && object.getClass().isArray()) {
    StrictMap<Object> map = new StrictMap<>();
    map.put("array", object);
    return map;
  }
  return object;
}

又是熟悉的代码,由于我们传入进来是map,所以这儿都不会执行,所以会执行下面的代码

public class CachingExecutor implements Executor { 
	@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
}

由于我们是执行新增的操作,所以这儿要刷新缓存,缓存的机制我们后面会讲,这儿直接跳过了。我们直接看delegate.update(ms, parameterObject);方法,具体的代码如下:

public abstract class BaseExecutor implements Executor {
	@Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
}

走来会清楚本地的缓存,然后执行doUpdate(ms, parameter);方法,我们继续跟进,具体的代码如下:

public class SimpleExecutor extends BaseExecutor {
	@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);
    }
  }
}

上面的代码走来直接先获取对应的配置类,然后执行configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);方法,我们跟进去看看,具体的代码如下:

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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

上面代码会执行new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);代码,我们继续跟进,具体代码如下:

public class RoutingStatementHandler implements StatementHandler {
	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());
    }
  }
}

到这儿所有的东西已经准备好了,于是会执行new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);方法,我们继续跟进对应的代码具体如下:

public class PreparedStatementHandler extends BaseStatementHandler {
  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }
}

可以发现调用的是父类BaseStatementHandler的方法,具体的代码如下:

public abstract class BaseStatementHandler implements StatementHandler {
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      //生成主键
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
    this.boundSql = boundSql;
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
}

这儿进行了一系列的参数的封装,最主要的方法还是if中判断,在这个判断中会自动生成主键,然后我们继续查看mappedStatement.getBoundSql(parameterObject);方法,这儿会将参数进行绑定,具体的代码如下:

public final class MappedStatement {
	public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }
    return boundSql;
  }
}

可以看到上面是进行SQL语句转换。会将一些占位符进行转换。然后返回的SQL就是正常的,同时参数的类型和值也放在这个对象中,最终返回,然后会执行prepareStatement(handler, ms.getStatementLog());方法,具体的代码如下:

public class SimpleExecutor extends BaseExecutor {
	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;
  }
}

上面代码handler.parameterize(stmt);是进行的参数绑定,我们继续跟进去看看:

public class PreparedStatementHandler extends BaseStatementHandler {
	@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  
  @Override
  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);
          }
        }
      }
    }
  }
}

上面进行一些SQL的参数的绑定,和前面的查询是一样的。这个时候对应的SQL语句已经生成,然后就需要执行对应的JDBC的操作,就可以插入一条数据了。然后执行update方法,直接跟进对应的代码,具体的代码如下:

public class RoutingStatementHandler implements StatementHandler {	
  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }
  
  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //执行底层的JDBC方法
    ps.execute();
    //获取受影响的函数
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
}

执行完,返回受影响的行数,然后关闭对应Statement。这个时候整个insert就执行完了。

然后我们再来看下update的方法。这儿我只会讲不同的地方。前面都是一样,到了参数的转换的地方,由于是一个参数,所以会直接将这个对象进行返回。这个时候内容如下:

id = 177
username = "xixi"
password = "321"

你会发现最终调用如下的方法

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

和上面的新增最终的调用的方法是一样的。所以后面调用的流程是一样的,然后删除调用的方法也是这样。至此整个增删改就讲完了。后面会继续讲mybatis其他的东西。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值