Reading and writing data in Mybatis

Keywords: Programming JDBC SQL Mybatis Database

The nature of data reading and writing

No matter which ORM framework, the essence of data reading and writing is to encapsulate JDBC. Its main purpose is to simplify the development process of JDBC, so that developers pay more attention to business. The following is the core process of JDBC:

  1. Register JDBC driver (Class.forName("XXX");)
  2. Open connection (DriverManager.getConnection("url","name","password"))
  3. Create a Statement (conn.prepareStatement(sql)) based on the connection
  4. Set parameters (stmt.setString(1, "wyf");)
  5. Execute query (stmt.executeQuery();)
  6. Processing results, result set mapping (resultSet.next())
  7. Close resources (finally)

Mybatis is also encapsulating JDBC. It gives the registration driver and open connection to the database connection pool. Mybatis supports the third-party database connection pool, and can also use its own database connection pool. It creates the Statement and executes the query to the StatementHandler. It sets the parameters to the ParameterHandler. The last two steps are to the ResultSetHandler To take charge.

Internal operation process of Executor

Test method org.apache.ibatis.binding.bindingtest ා shouldfindthreespecicposts

The following is a simple data reading and writing process. Let's understand the function of each component in the whole data reading and writing from a macro perspective:

The data reading and writing of Mybatis is mainly realized through three components, StatementHandler, ParameterHandler and ResultSetHandler, which are coordinated by using Excel

  • StatementHandler: it is used to perform operations using the Statement or PrepareStatement of the database, enabling the connection between the preceding and the following;
  • ParameterHandler: set parameters for precompiled SQL statements. Placeholder "? In SQL statements All of them correspond to an element in the BoundSql.parameterMappings collection, in which the corresponding parameter name and its related attributes are recorded
  • ResultSetHandler: encapsulates the result set returned by the database and returns the entity type specified by the user;

StatementHandler

StatementHandler class diagram

RoutingStatementHandler

StatementHandler is created by StatementType, and method call is completed by static agent mode, which mainly plays the role of routing. It's the component that the exciter component actually instantiates.

public class RoutingStatementHandler implements StatementHandler {
  /**
   * Static agent mode
   */
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // Create different implementation classes (policy patterns) according to {@ link org.apache.ibatis.mapping.StatementType}
    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());
    }
  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }
...
}

BaseStatementHandler

The abstract parent class of all subclasses defines the operation sequence of initializing statements. The subclass implements the concrete instantiation of different statements (template patterns).

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // Instantiate Statement (implemented by subclass) [template method + policy mode]
    statement = instantiateStatement(connection);
    // Set timeout
    setStatementTimeout(statement, transactionTimeout);
    // Set the number of data records obtained
    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);
  }
}

protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

instantiateStatement() is a template method, which is implemented by a subclass.

SimpleStatementHandler

The JDBC statement execution mode is used without parameter processing. The source code is as follows:

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // Instantiate Statement
  if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.createStatement();
  } else {
    return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}

@Override
public void parameterize(Statement statement) {
  // N/A
  // Statement is used to execute sql directly, so there are no parameters
}

PreparedStatementHandler

Precompile the execution mode using JDBC Preparedstatement.

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // Instantiate PreparedStatement
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      return connection.prepareStatement(sql, keyColumnNames);
    }
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}

@Override
public void parameterize(Statement statement) throws SQLException {
  // Parameter processing
  parameterHandler.setParameters((PreparedStatement) statement);
}

CallableStatementHandler

Use the JDBC callablestatement execution mode to call stored procedures. It's rarely used now.

ParameterHandler

The main function is to set parameters for PreparedStatement. The source code is as follows:

@Override
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // Get parameter mapping relationship
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // Loop to get processing parameters
    for (int i = 0; i < parameterMappings.size(); i++) {
      // Get the parameters of the corresponding index bit
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        // Get parameter name
        String propertyName = parameterMapping.getProperty();
        // Determine whether it is an additional parameter
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        }
        // Judge whether there is no parameter
        else if (parameterObject == null) {
          value = null;
        }
        // Determine whether the parameter has a corresponding TypeHandler
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // None of the above. Get the value through reflection
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // Get the type processor of the parameter
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          // Set parameters according to TypeHandler
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}
  1. Get parameter mapping relationship
  2. Get parameter name
  3. Get parameter value according to parameter name
  4. Get the type processor of the parameter
  5. Set parameter value according to TypeHandler

Through the above process, it can be found that the real parameter setting is implemented by TypeHandler.

TypeHandler

Mybatis basically provides all the typehandlers we need, but we can also implement it ourselves. The main functions of TypeHandler are:

  1. Set the PreparedStatement parameter value
  2. Get query result value
public interface TypeHandler<T> {

  /**
   * Set parameter value for {@ link PreparedStatement}
   *
   * @param ps        {@link PreparedStatement}
   * @param i         Index bit of parameter
   * @param parameter Parameter value
   * @param jdbcType  Parameter type
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Get result value based on column name
   *
   * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  /**
   * Get result value according to index bit
   */
  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  /**
   * Get stored procedure result value
   */
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

The essence of TypeHandler is to encapsulate stmt.setString(1, "wyf"); and resultSet.getString("name") in JDBC. The complete JDBC code can be viewed Key points of JDBC interview.

ResultSetHandler

The main function of ResultSetHandler is to encapsulate the ResultSet returned by the database, and automatically map it through ResultMap configuration and reflection to return the entity type specified by the user. The core idea is as follows:

  1. Paging according to RowBounds
  2. Instantiate the target class according to the return value type of ResultMap configuration and constructor configuration information
  3. According to the mapping relationship configured by ResultMap, get the TypeHandler, and then get the value from the ResultSet
  4. According to the mapping relationship configured by ResultMap, get the attribute name of the target class, and then assign a value to the target class through reflection

There are too many source codes, so we will not send them here. If you are interested, please look at the comments on the source code below. The flow chart below will draw the method call stack.

Mybatis's own RowBounds page is a logical page. If there is a large amount of data, there may be memory overflow. Therefore, it is not recommended to use mybatis's default page.

Data reading flow chart

summary

The whole data reading process of Mybatis is actually a standard implementation of JDBC.

Chinese notes to Mybatis source code

https://github.com/xiaolyuh/mybatis

Posted by harman on Tue, 26 Nov 2019 00:54:05 -0800