mybatis source code interpretation II - full analysis of call process

Official documents

Interpret according to the jdbc stage of preliminary preparation

  1. to configure
  2. Splicing sql statements
  3. Load the data source and get the Connection
  4. Get Statement
  5. Execute get results
  6. Result conversion
  7. close
  8. exception handling
Example 1: com.dzq.MybatisSessionTest

The most basic function, read the source code according to this example

public static void main(String[] args) throws IOException {
    // configuration file
    String resource = "mybatis-config.xml";
    // Parsing configuration files
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =
            new SqlSessionFactoryBuilder().build(inputStream);
    // Get session
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // Find the sql statement according to the state and pass in the parameters for execution
    String flowKey = sqlSession.selectOne("com.dzq.mapper.TransactionMapper.getFlowKey", 10);
    System.out.println(flowKey);
}
Example 2: com.dzq.mybatismapppertest

How to generate a dynamic agent Mapper and what the dynamic agent does: how to find the corresponding sql according to the dynamic agent

public static void main(String[] args) throws IOException {
    // configuration file
    String resource = "mybatis-config.xml";
    // Parsing configuration files
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =
            new SqlSessionFactoryBuilder().build(inputStream);
    // Get session
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // Get mapper proxy
    TransactionMapper transactionMapper = sqlSession.getMapper(TransactionMapper.class);
    // Execute the proxy method and convert the result
    String flowKey = transactionMapper.getFlowKey(10);
    System.out.println(flowKey);
}

The configuration file mybatis-config.xml and the corresponding TransactionMapper.xml

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.9.224.45:3306/activiti?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC&amp;useSSL=false" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/TransactionMapper.xml"/>
    </mappers>
</configuration>
<mapper namespace="com.dzq.mapper.TransactionMapper">
   <select id="getFlowKey" resultType="string">
      select flow_key from transaction where id = #{id}
   </select>
</mapper>

1. Configuration

When we don't know the overall design of mybatis, too many interpretations of each attribute and parameter of the configuration actually don't make much sense. At least we know some configuration parameters through the jdbc preview above, but we can understand them at this stage

  1. What configuration mode does mybatis use? Is it properties or xml or database support.
  2. How is the configuration loaded during parsing? What kind of design pattern is designed?
Configuration and selection

The configuration mode selected by mybatis is the xml file used. xml can well use the label corresponding class, and can also well describe the relationship and dependency between classes.

load
String resource = "mybatis-config.xml";
// Find the file in the classpath to get the input source: the parsing parameters of many files are InputStream, which can come from the network: (the application layer can be a distributed file system, http/https, etc.) or from the local
InputStream inputStream = Resources.getResourceAsStream(resource);
// When you get the data source, you can get the data and then parse it. After parsing, a SqlSessionFactory factory is generated. SqlSession is a class that directly executes sql
// The corresponding factory is generated through configuration. Because the "part" is known, the corresponding "product" can be created through the part
SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(inputStream);
Loading process
  1. Create a special parsing class XMLConfigBuilder to inherit BaseBuilder

    // inputStream can get all the configuration information with input, but a special class is created for parsing
    // The parser stores the configuration of all parsed results and whether they have been parsed. Is it the XPathParser that is responsible for parsing xml
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // Construction method 
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        // The first parameter, xpath parser, is the xpath parser used
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    
  2. analysis

    public Configuration parse() {
      if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
      parsed = true;
      // Resolve root label configuration
      parseConfiguration(parser.evalNode("/configuration"));
      // Return configuration to build SqlSessionFactory:SqlSessionFactory has only one attribute, that is, it
      return configuration;
    }
    
  3. /configuration tag parsing

    // Analyze each node
    private void parseConfiguration(XNode root) {
      try { 
        propertiesElement(root.evalNode("properties"));
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        // Focus on the analysis environment
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
      } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
      }
    }
    
  4. Introduce XNode class design

    Xnode is the encapsulation of nodes. It refers to the node node in the jdk and encapsulates all functions of the node node. It is the node adapter. Some values of the node node have been put into Xnode, such as attributes.

    1. Why not put all the values into Xnode? So you don't have to have a reference to it

      node has many attributes, mainly related to the acquisition of child nodes. There may be many child nodes. Lazy loading is used in this place.

    private final Node node;
    private final String name;
    private final String body;
    private final Properties attributes;
    private final Properties variables;
    private final XPathParser xpathParser;
    
    public XNode(XPathParser xpathParser, Node node, Properties variables) {
      this.xpathParser = xpathParser;
      this.node = node;
      this.name = node.getNodeName();
      this.variables = variables;
      this.attributes = parseAttributes(node);
      this.body = parseBody(node);
    }
    
  5. environments tag parsing

    private void environmentsElement(XNode context) throws Exception {
      if (context != null) {
        if (environment == null) {
          environment = context.getStringAttribute("default");
        }
        // Query all sub tag environment s
        for (XNode child : context.getChildren()) {
          String id = child.getStringAttribute("id");
          if (isSpecifiedEnvironment(id)) {
            // Transaction resolution
            TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
            // Data source analysis, focusing on explanation
            DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
            DataSource dataSource = dsFactory.getDataSource();
            // assignment
            Environment.Builder environmentBuilder = new Environment.Builder(id)
                .transactionFactory(txFactory)
                .dataSource(dataSource);
            // assignment
            configuration.setEnvironment(environmentBuilder.build());
          }
        }
      }
    }
    

    Parsing process core class diagram

  6. DataSourceFactory resolution

    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
      if (context != null) {
        String type = context.getStringAttribute("type");
        // Subtag: < property name = "driver" value = "com. Mysql. JDBC. Driver" / >
        // All child tags are Properties, so they are directly converted to Properties
        Properties props = context.getChildrenAsProperties();
        // Get the class file from the Map object according to the type return, and then create it
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        // Like the SqlSessionFactory factory, it also needs "parts" to produce "products"
        // The materials here use Properties in order to adapt to all label factory modes, such as TransactionFactory
        // Reflect DataSourceFactory by creating MetaObject
        factory.setProperties(props);
        return factory;
      }
      throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }
    
    
    
  7. Create DefaultSqlSessionFactory

    new DefaultSqlSessionFactory(config);// The DefaultSqlSessionFactory property has only one config
    

2. Splicing sql

mybatis writes all sql statements, but the variables are placeholders. All sql is in the configuration file, so it has been parsed when parsing the configuration file. Look at the parsing file.

// Parse the mappers tag. The original configuration file refers to a mapper xml file. Jump to the parse xml file again
mapperElement(root.evalNode("mappers"));

Parse mapper.xml file

// org.apache.ibatis.builder.xml.XMLMapperBuilder
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // Parsing mapper tags is critical
    // Go back to the parsing tag org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode directly. There are many attributes to parse, but they are relatively simple. You can only know the location
    // Finally, it is encapsulated in config.mappedStatements. key: id,value:MappedStatement. The specific parameters can be directly viewed from this class
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    // Parsing Mapper.java interface
    // Finally, it is encapsulated in config.knownMappers, key:type(com.dzq.mapper.TransactionMapper, Class type), value: MapperProxyFactory
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

3. Load the data source and obtain the Connection

// Execution type, from config, transaction level, auto commit
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // Get environment
    final Environment environment = configuration.getEnvironment();
    // Transaction factory
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // Put it into dataSource. dataSource can get Connection
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // If there is tx in the actuator and it is called according to the configuration, you can get other configurations
    final Executor executor = configuration.newExecutor(tx, execType);
    // Create a session, configure an actuator, and automatically execute it
    // It's everywhere
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

4. Get Statement

At the beginning of execution, you can execute after obtaining SqlSession. SqlSession is the entry for execution. First look at this interface

// query
<T> T selectOne(String statement);
<E> List<E> selectList(String statement);
// modify
int update(String statement);
// insert
int insert(String statement);
// delete
int delete(String statement);

First, check the source code of the selectList. Imagine the process: get the return value and parameters of the sql statement according to the statement, execute the selectList statement according to the executor, get the results, and encapsulate the return according to the return value.

Two key classes MappedStatement and Executor

// SqlSession class main properties and methods
// The main attributes, configuration, executor and autoCommit, are created during construction. dirty and cursorList are intermediate states of execution
// The reference of configuration can get all configurations
private final Configuration configuration;
// Executor, real execution class
private final Executor executor;
// Auto submit
private final boolean autoCommit;
// Dirty data
private boolean dirty;
// Store all cursor information. When public < T > cursor < T > selectcursor (string statement) is called, all return values will be stored
private List<Cursor<?>> cursorList;
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // MappedStatement is obtained from config and parsed by config. MappedStatement is assigned when parsing configuration
    MappedStatement ms = configuration.getMappedStatement(statement);
    // Execution, and then look at the execution method
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
// Executor key properties and methods
// Encapsulated actuator, built-in adapter mode
protected Executor delegate;
// Transaction with dataSource
protected Transaction transaction;
// to configure
protected Configuration configuration;
// Query, ms:mapper attribute, parameter: parameter, rowboundaries: paging, ResultHandler: result processing
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   // Assemble sql according to parameter splicing. A simple sql also needs to be encapsulated. See how to encapsulate it
   // BoundSql key attribute, sql: sql statement, with wildcards, parameterMappings: parameter, id, type, parameterObject: parameter value
   BoundSql boundSql = ms.getBoundSql(parameter);
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // If the cache is empty, you can directly see whether the query method of delegate is an adaptation mode or an Executor
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // delegate real executor execution
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // Get config
    Configuration configuration = ms.getConfiguration();
    // Get statementHandler, and create statementHandler with config, which is also an adapter mode
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    /** configuration.newStatementHandler Method introduction start
    //StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);The following are the key properties and construction methods of RoutingStatementHandler. The real call uses the delegate call
    //private final StatementHandler delegate;
    //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());
    // }

    //}
    configuration.newStatementHandler Method introduction
    **/
    //Generate jdbc statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    /**prepareStatement Internal method
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
      Statement stmt;
      // Generate connection
      Connection connection = getConnection(statementLog);
      // prepare The template mode is described in the following code
      stmt = handler.prepare(connection, transaction.getTimeout());
      // Assignment parameters have different effects for different statement s
      handler.parameterize(stmt);
      return stmt;
    }
    **/
    // statement execute query
    // statementHandler query, processing stmt
    return handler.<E>query(stmt, resultHandler);
  } finally {
    // Close stmt
    closeStatement(stmt);
  }
}

org.apache.ibatis.executor.statement.BaseStatementHandler#prepare template method

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // There are two core methods for generating statements: PreparedStatementHandler, simplestationhandler and instantiateStatement abstract methods
    statement = instantiateStatement(connection);
    /**PreparedStatementHandler Instantiatestatestatement method for
    protected Statement instantiateStatement(Connection connection) throws SQLException {
      String sql = boundSql.getSql();
      // There is no design pattern, and different methods are called to generate statement according to different situations
      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() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
      } else {
        return connection.prepareStatement(sql);
      }
    }
    **/
    // Set timeout
    setStatementTimeout(statement, transactionTimeout);
    // Set the number of fetches each time
    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);
  }
}
  1. Execute get result handler.query(stmt, resultHandler);

    // org.apache.ibatis.executor.statement.PreparedStatementHandler query results
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
      PreparedStatement ps = (PreparedStatement) statement;
      // Execute. Next, process the results
      ps.execute();
      // resultSetHandler is one of the properties of PreparedStatementHandler
      return resultSetHandler.<E> handleResultSets(ps);
    }
    

6. Result conversion resultSetHandler. handleResultSets(ps);

The resultSetHandler class is created through the property of resultSetHandler created when StatementHandler is created

configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  final List<Object> multipleResults = new ArrayList<Object>();

  int resultSetCount = 0;
  // Get ResultSetWrapper
  // ResultSetWrapper design: first of all, RS has some other attributes. If you only hold the reference of RS and need to repeatedly obtain some other attributes obtained through RS when calling other methods, you can directly convert rs to the attribute of ResultSetWrapper. The following is the encapsulation of rs
  //public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
  //  super();
  //  Encapsulation of other attributes, typeHandlerRegistry, and some attributes converted by rs
  //  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  //  this.resultSet = rs;
  //  final ResultSetMetaData metaData = rs.getMetaData();
  //  final int columnCount = metaData.getColumnCount();
  //  for (int i = 1; i <= columnCount; i++) {
  //    Returned column name
  //    columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
  //    Returned jdbc type: such as varchar
  //    jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
  //    Returned Java type: for example, java.lang.String
  //    classNames.add(metaData.getColumnClassName(i));
  //  }
  //}
  // Take out the fi rs t one
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // The return type recorded according to the configuration file
  // Resulttype and resultmap corresponding to the configuration file
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  while (rsw != null && resultMapCount > resultSetCount) {
    // Take out the resultMap and put the value of the corresponding result into multipleResults
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // Put structure into multipleResults
    handleResultSet(rsw, resultMap, multipleResults, null);
    // Get the next result
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }
  // Returns a single result
  // multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults
  return collapseSingleResultList(multipleResults);
}

Processing handleResultSet results

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {// The default is empty
        // Get the default interface processor
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        // Processing results
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        /**
        Calling method: handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?>           resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
          DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
          // skip Number of articles
          skipRows(rsw.getResultSet(), rowBounds);
          // Judge whether to stop and cannot exceed the configured number
          while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
            // Because resultMap.getDiscriminator() is empty, it will directly return resultMap itself
            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
              // Resolve database values
              Object rowValue = getRowValue(rsw, discriminatedResultMap);
                // getRowValue Internal method
                Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
                  // createResultObject Internal method
                  Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
                    // Create the result of the original type and judge that the returned type is in the basic data type
                    return createPrimitiveResultObject(rsw, resultMap, columnPrefix)
                    //2021-10-25 Supplement the entity class to obtain the resultObject method, and explain how to assign values in the next code
                    return objectFactory.create(resultType);// objectFactory To create an instance reconciliation, and assign a value after returning
                      // The type processing class is obtained according to the resultType, which is obtained from rsw and can be cached
                      final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
                      // Corresponding type processing
                      return typeHandler.getResult(rsw.getResultSet(), columnName);
              // Put the field value into resultContext and resultHandler
              storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
          }
        }
        **/
        // Take out the results and put them into the multipleResults result set
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}

On October 25, 2021, the content of assigning values to non original classes was added

// Get examples
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// Continue creating createResultObject
// Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
// objectFactory.create(resultType); Create instances from reflections through objectfactory
// If there is a non default constructor
// createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
// final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
// Continue to call createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
/**
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, 			List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
    boolean foundValues = false;
    // Loop parameter list, and obtain the column name in rsw according to the subscript
    for (int i = 0; i < constructor.getParameterTypes().length; i++) {
      Class<?> parameterType = constructor.getParameterTypes()[i];
      String columnName = rsw.getColumnNames().get(i);
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
      // Value based on column name
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
      constructorArgTypes.add(parameterType);
      // Put into the construction parameter array
      constructorArgs.add(value);
      foundValues = value != null || foundValues;
    }
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
**/
// After obtaining the entity, judge that it is not the basic data type, enter the judgment statement and assign again
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    // Create metadata objects,
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    // Create MetaObject
    /**
    new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    //It is neither an Array nor a List, so the BeanWrapper is created directly, which mainly contains the get and set method information inside the Class
    this.objectWrapper = new BeanWrapper(this, object);
    **/
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    // Assign values to instances
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}

7. Close

Statement closing: you can see that the generation is generated from the Executor. The processing of statement is processed in other classes, so it is also closed in this class

// SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  // generate
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    // close
    closeStatement(stmt);
  }
}

ResultSet close: the ResultSet is obtained from org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets and encapsulated in the ResultSetWrapper. The value is in handleResultSet, and then the next ResultSet is removed. Close in handleResultSet

// Encapsulated in ResultSetWrapper
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // Close after value taking
    closeResultSet(rsw.getResultSet());
  }
}

Connection closing: the Transaction is created in advance when creating a SqlSession and then when creating an executor, so it is kept indirectly by the SqlSession and closed by the SqlSession.

Statement and ResultSet are generated during each execution and need to be closed every time. Connection is a link, so it can be closed when not in use.

8. Exception handling

Loading file: exception needs to be handled IOException:IO exception is an exception prepared by mybatis in advance, and it has not really been linked to the database. File exception cannot be handled. It needs to be considered when writing.

Start execution: you don't need to handle exceptions. You can execute directly and check errors during execution. Key analysis is needed. The key exception SQLException of JDBC operation is the exception that needs to be caught. How to handle it?

At the beginning of execution, there are two steps: 1: preliminary preparation, mybatis custom exception, 2: JDBC and SQLException

The prepared exceptions are all runtimeexceptions, which do not need to be caught and handled when there is an error, such as

@Override
public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.<T>selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    // Exception handling when there is too much query data
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

All exceptions involving JDBC are not handled, but exceptions are caught at the outermost layer

// All exceptions involving jdbc are thrown
@Override
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameter);
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    // Call the code above
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    // Capture all exceptions, encapsulate them, and return custom exceptions
    /**
    public static RuntimeException wrapException(String message, Exception e) {
    	return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
    }
    **/
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

For null pointer verification, try to encapsulate it into methods, such as the above code

MappedStatement ms = configuration.getMappedStatement(statement); // The mappedstatement obtained from the configuration is not verified to be empty. If it is empty, it will be null pointerexception. Where is the verification? View configuration. Getmappedstatement (statement);

Think of another problem, the data structure must be reasonable

Calling code

configuration.getMappedStatement(statement);
get into
this.getMappedStatement(id, true);
get into
//Get the value from the mappedStatements of the configuration attribute, and the exception has not been verified
//protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
return mappedStatements.get(id);
//The StrictMap class gets, rewrites the get method, and throws an exception at the bottom layer, not at the business layer
public V get(Object key) {
   V value = super.get(key);
   if (value == null) {
      // If it is empty, an exception will be thrown, and the exception information prompt is also very accurate. name is the StrictMap attribute, naming the map.
      throw new IllegalArgumentException(name + " does not contain value for " + key);
   }
   if (value instanceof Ambiguity) {
      throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
   }
   return value;
}

Write your own examples

public class Student {
    private String name;
    // How many addresses do students have
    private List<Address> addressList;
    public List<Address> getAddressList() {
        if (addressList == null || addressList.size() == 0) {
            throw new RuntimeException(name + " Home address not filled in");
        }
        return addressList;
    }
    public static void main(String[] args) {
        Student student = new Student();
        // It does not affect the appearance of business code
        List<Address> addressList = student.getAddressList();
        //TODO
    }
}

Tags: JSP Container JavaSE unicode

Posted on Wed, 01 Dec 2021 04:06:01 -0500 by unclebob