zoukankan      html  css  js  c++  java
  • 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(下篇)

    Configuration的newStatementHandler分析

    SimpleExecutor的doUpdate方法上文有分析过:

     1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.update(stmt);
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11 }

    这两天重看第5行的newStatementHandler方法的时候,发现方法上文在这个方法中分析地太简略了,这里过一遍一下Configuration的newStatementHandler方法,方法的实现为:

    1 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    2     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    3     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    4     return statementHandler;
    5 }

    第3行的代码是加入插件没什么好看的,看下第2行的代码,StatementHandler接口真正实例化出来的是RoutingStatementHandler,构造方法实现为:

     1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
     2 
     3     switch (ms.getStatementType()) {
     4       case STATEMENT:
     5         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
     6         break;
     7       case PREPARED:
     8         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
     9         break;
    10       case CALLABLE:
    11         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    12         break;
    13       default:
    14         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    15     }
    16 
    17 }

    RoutingStatementHandler同样是装饰器模式的实现,实现了StatementHandler接口并持有StatementHandler接口引用delegate。这里StatementType的为PREPARED,因此执行的第7行的判断,实例化出PreparedStatementHandler,实例化的过程为:

     1 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
     2     this.configuration = mappedStatement.getConfiguration();
     3     this.executor = executor;
     4     this.mappedStatement = mappedStatement;
     5     this.rowBounds = rowBounds;
     6 
     7     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
     8     this.objectFactory = configuration.getObjectFactory();
     9 
    10     if (boundSql == null) { // issue #435, get the key before calculating the statement
    11       generateKeys(parameterObject);
    12       boundSql = mappedStatement.getBoundSql(parameterObject);
    13     }
    14 
    15     this.boundSql = boundSql;
    16 
    17     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    18     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    19 }

    这里的重点是BoundSql,它可以通过MappedStatement获取到,BoundSql中存储了几个重要的内容:

    1. 参数对象本身
    2. 参数列表
    3. 待执行的SQL语句

    有些基于MyBatis二次开发的框架通常都会拿到BoundSql中的SQL语句进行修改并重新设置进BoundSql中。

    生成Statement

    上文已经写了生成Connection的流程,本文继续看,首先还是再贴一下SimpleExecutor的prepareStatement方法:

    1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    2     Statement stmt;
    3     Connection connection = getConnection(statementLog);
    4     stmt = handler.prepare(connection, transaction.getTimeout());
    5     handler.parameterize(stmt);
    6     return stmt;
    7 }

    接着就是第4行的代码,生成Statement了,第4行的代码实现为:

    1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    2     return delegate.prepare(connection, transactionTimeout);
    3 }

    delegate上文是装饰器模式中的被装饰角色,其接口类型为StatementHandler,真实类型为PreparedStatementHandler,这个在最开头的部分已经分析过了。看一下prepare方法实现:

     1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
     2     ErrorContext.instance().sql(boundSql.getSql());
     3     Statement statement = null;
     4     try {
     5       statement = instantiateStatement(connection);
     6       setStatementTimeout(statement, transactionTimeout);
     7       setFetchSize(statement);
     8       return statement;
     9     } catch (SQLException e) {
    10       closeStatement(statement);
    11       throw e;
    12     } catch (Exception e) {
    13       closeStatement(statement);
    14       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    15     }
    16 }

    第6行的代码设置的是查询超时时间、第7行的代码设置的是接收的数据大小,就不跟进去看了,接着看下第6行的instantiateStatement方法实现:

     1 protected Statement instantiateStatement(Connection connection) throws SQLException {
     2     String sql = boundSql.getSql();
     3     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
     4       String[] keyColumnNames = mappedStatement.getKeyColumns();
     5       if (keyColumnNames == null) {
     6         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
     7       } else {
     8         return connection.prepareStatement(sql, keyColumnNames);
     9       }
    10     } else if (mappedStatement.getResultSetType() != null) {
    11       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    12     } else {
    13       return connection.prepareStatement(sql);
    14     }
    15 }

    第2行,从boundSql中获取真正的SQL语句,第一部分已经分析过了。拿到SQL语句之后,执行第3行与第5行的判断,这里就是我们熟悉的通过Connection拿Statement的代码了,通过prepareStatement方法获取到PreparedStatement,其真实类型为com.mysql.jdbc.JDBC4PreparedStatement,是PreparedStatement的子类。

    Statement参数设置

    获取了Statement后,下一步就是设置参数了,看一下设置参数的代码,还是回到SimpleExecutor的prepareStatement方法:

    1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    2     Statement stmt;
    3     Connection connection = getConnection(statementLog);
    4     stmt = handler.prepare(connection, transaction.getTimeout());
    5     handler.parameterize(stmt);
    6     return stmt;
    7 }

    跟第5行的代码:

     1 public void parameterize(Statement statement) throws SQLException {
     2     parameterHandler.setParameters((PreparedStatement) statement);
     3 }

    继续跟第2行的代码:

     1 public void setParameters(PreparedStatement ps) {
     2     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     3     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
     4     if (parameterMappings != null) {
     5       for (int i = 0; i < parameterMappings.size(); i++) {
     6         ParameterMapping parameterMapping = parameterMappings.get(i);
     7         if (parameterMapping.getMode() != ParameterMode.OUT) {
     8           Object value;
     9           String propertyName = parameterMapping.getProperty();
    10           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
    11             value = boundSql.getAdditionalParameter(propertyName);
    12           } else if (parameterObject == null) {
    13             value = null;
    14           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    15             value = parameterObject;
    16           } else {
    17             MetaObject metaObject = configuration.newMetaObject(parameterObject);
    18             value = metaObject.getValue(propertyName);
    19           }
    20           TypeHandler typeHandler = parameterMapping.getTypeHandler();
    21           JdbcType jdbcType = parameterMapping.getJdbcType();
    22           if (value == null && jdbcType == null) {
    23             jdbcType = configuration.getJdbcTypeForNull();
    24           }
    25           try {
    26             typeHandler.setParameter(ps, i + 1, value, jdbcType);
    27           } catch (TypeException e) {
    28             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    29           } catch (SQLException e) {
    30             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    31           }
    32         }
    33       }
    34     }
    35 }

    最终执行的是第26行的代码,从26行的代码我们可以知道,参数设置到最后都是通过参数的TypeHandler来执行的,JDBC为我们预定义了很多TypeHandler,比如int值的TypeHandler就是IntegerTypeHandler,当然我们也可以定义自己的TypeHandler,通常来说继承BaseTypeHandler就可以了。

    但是在此之前,会获取到Statement(setParameters方法形参)、占位符位置号(for循环的遍历参数i)、参数值(通过属性名获取)与jdbcType(配置在配置文件中,默认为null),最终执行TypeHandler的setParameters方法,这是BaseTypeHandler中的一个方法:

     1 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
     2     if (parameter == null) {
     3       if (jdbcType == null) {
     4         throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
     5       }
     6       try {
     7         ps.setNull(i, jdbcType.TYPE_CODE);
     8       } catch (SQLException e) {
     9         throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
    10                 "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
    11                 "Cause: " + e, e);
    12       }
    13     } else {
    14       try {
    15         setNonNullParameter(ps, i, parameter, jdbcType);
    16       } catch (Exception e) {
    17         throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
    18                 "Try setting a different JdbcType for this parameter or a different configuration property. " +
    19                 "Cause: " + e, e);
    20       }
    21     }
    22 }

    这里的参数不为null,走13行的else,执行setNonNullParameter方法,这是IntegerTypeHandler中的一个方法:

     1 public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
     2       throws SQLException {
     3     ps.setInt(i, parameter);
     4 }

    这里的代码就比较熟悉了,PreparedStatement的setInt方法。

    执行更新操作并处理结果

    最后一步,执行更新操作并对结果进行处理,回到SimpleExecuto的doUpdate方法:

     1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.update(stmt);
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11 }

    第6行已经准备好了Statement,第7行执行update操作并对结果进行处理并返回:

     1 public int update(Statement statement) throws SQLException {
     2     return delegate.update(statement);
     3 }

    这里的委托delegate前面已经说过了,其真实类型是PreparedStatementHandler,update方法的实现为:

    1 public int update(Statement statement) throws SQLException {
    2     PreparedStatement ps = (PreparedStatement) statement;
    3     ps.execute();
    4     int rows = ps.getUpdateCount();
    5     Object parameterObject = boundSql.getParameterObject();
    6     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    7     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    8     return rows;
    9 }

    第3行的execute方法是PreparedStatement中的方法,execute方法执行操作,然后第4行通过getUpdateCount()方法获取本次操作更新了几条数据,作为最终的值返回给用户。

    第5行的代码通过BoundSql获取参数对象,这里是MailDO对象,因为我们知道在插入场景下,开发者是有这种需求的,需要返回插入的主键id,此时会将主键id设置到MailDO中。

    第6行的代码通过MappedStatement获取KeyGenerator,一个主键生成器。

    第7行的代码做了一个操作完毕的后置处理:

     1 public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
     2     processBatch(ms, stmt, getParameters(parameter));
     3 }

    首先将对象包装成集合类型,然后跟第2行的代码processBatch方法:

     1 public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
     2     ResultSet rs = null;
     3     try {
     4       rs = stmt.getGeneratedKeys();
     5       final Configuration configuration = ms.getConfiguration();
     6       final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
     7       final String[] keyProperties = ms.getKeyProperties();
     8       final ResultSetMetaData rsmd = rs.getMetaData();
     9       TypeHandler<?>[] typeHandlers = null;
    10       if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
    11         for (Object parameter : parameters) {
    12           // there should be one row for each statement (also one for each parameter)
    13           if (!rs.next()) {
    14             break;
    15           }
    16           final MetaObject metaParam = configuration.newMetaObject(parameter);
    17           if (typeHandlers == null) {
    18             typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
    19           }
    20           populateKeys(rs, metaParam, keyProperties, typeHandlers);
    21         }
    22       }
    23     } catch (Exception e) {
    24       throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    25     } finally {
    26       if (rs != null) {
    27         try {
    28           rs.close();
    29         } catch (Exception e) {
    30           // ignore
    31         }
    32       }
    33     }
    34 }

    简单说这里就是遍历集合,通过JDBC4PreparedStatement的getGeneratedKeys获取ResultSet,然后从ResultSet中使用getLong方法获取生成的主键,设置到MailDO中。完成整个操作。

    最后,本文演示的是insert数据的update方法流程,前文已经说过insert、update、delete在MyBatis中都是一样的,因此update、delete也是一样的操作,这里就不再赘述了。

  • 相关阅读:
    centos下 yum安装ngix
    [转]ORACLE函数大全
    Oracle的DML语言必备基础知识
    微信公众账号开发教程
    freemarker页面如何获取绝对路径basePath
    使用intellij的svn时提示出错: Can't use Subversion command line client: svn.Errors found while svn working copies detection.
    网站地址
    如何让tomcat不记录catalina.out这个日志文件
    在centos6.7用yum安装redis解决办法
    剑指 Offer 06. 从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/xrq730/p/6827905.html
Copyright © 2011-2022 走看看