3. Mybatis Source Analysis

Posted by sareejoh on Fri, 29 Oct 2021 18:03:25 +0200

Catalog

1. Traditional methods

1.1 Source Profiling-Initialization

// 1. Read configuration file, read as byte input stream, note: not parsed yet
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

// 2. Parse the configuration file, encapsulate the Configuration object, create the DefaultSqlSessionFactory object
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

// 3. Produced DefaultSqlsession instance object set transaction not autocommit completed executor object creation
SqlSession sqlSession = sqlSessionFactory.openSession();

// 4. (1) Get the specified MappedStatement object from the map collection in Configuration based on statementid
//(2) Delegate query task to executor executor
User user =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
System.out.println(user);
User user2 =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
System.out.println(user2);

// 5. Release resources
sqlSession.close();

1.2 Initialization

// 1The build we initially called
public SqlSessionFactory build (InputStream inputStream){
		//Invoke overloaded method
    return build(inputStream, null, null);
}

    // 2. Called overloaded methods
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
          try {
              // Create XMLConfigBuilder, which is a class dedicated to parsing mybatis configuration files
              XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
              // Perform XML parsing
              // Create DefaultSqlSessionFactory object
              return build(parser.parse());
          } catch (Exception e) {
              throw ExceptionFactory.wrapException("Error building SqlSession.", e);
          } finally {
              ErrorContext.instance().reset();
              try {
                  inputStream.close();
              } catch (IOException e) {
                  // Intentionally ignore. Prefer previous error.
              }
          }
      }
      
     /**
     * 3 Parse the XML into a Configuration object.
     *
     * @return Configuration object
     */
    public Configuration parse() {
        // If resolved, throw BuilderException exception
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // Tag parsed
        parsed = true;
        ///parser is an XPathParser parser object that reads data within a node, <configuration>is the top-level tag in the MyBatis configuration file
        // Parse XML configuration node
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    
    
    
    /**
     * 4,Parsing XML
     *
     * For specific XML tags for MyBatis, see XML Mapping Profile http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root root node
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // Resolve <properties />tags
            propertiesElement(root.evalNode("properties"));
            // Resolve <settings />tags
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // Loading custom VFS implementation classes
            loadCustomVfs(settings);
            // Resolve <typeAliases />Tags
            typeAliasesElement(root.evalNode("typeAliases"));
            // Resolve <plugins />tags
            pluginElement(root.evalNode("plugins"));
            // Resolve <objectFactory />Tags
            objectFactoryElement(root.evalNode("objectFactory"));
            // Resolve <objectWrapperFactory />Tags
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // Resolve <reflectorFactory />Tags
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // Assign <settings /> to Configuration property
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // Resolve <environments />tags
            environmentsElement(root.evalNode("environments"));
            // Resolve <databaseIdProvider />Label
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // Resolve <typeHandlers />Tags
            typeHandlerElement(root.evalNode("typeHandlers"));
            // Resolve <mappers />tags
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    
    
     
// 5. Called overloaded methods
public SqlSessionFactory build(Configuration config) {
//The DefaultSqlSessionFactory object is created and passed into the Configuration object.
  return new DefaultSqlSessionFactory(config);
}
      
  • When MyBatis is initialized, it loads all the configuration information for MyBatis into memory and stores it using the org.apache.ibatis.session.Configuratio n instance

Introduction to the 1.2.1 Configuration object

 Configuration Object structure and xml Configuration files have nearly the same objects.
Review xml What are the configuration labels in:
properties (attribute),settings (Set up),typeAliases (Type Alias),typeHandlers (Type Processor),objectFactory (Object Factory),mappers (Mapper)etc. Configuration There are also corresponding object properties to encapsulate them
 That is, the essence of initializing profile information is creating Configuration Object that will be resolved xml Encapsulate data into Configuration In internal properties

Introduction to 1.2.2 MappedStatement

MappedStatement corresponds to a select/update/insert/delete node in the Mapper configuration file. The tags configured in mapper are encapsulated in this object and are used primarily to describe an SQL statement

 Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>
("Mapped Statements collection")

2. Source Profiling-Executing SQL Process

Introduction to 2.1 SqlSession

SqlSession is an interface that has two implementation classes: DefaultSqlSession (default) and SqlSession Manager (deprecated, not described)

SqlSession is the top-level class in MyBatis for interacting with databases and is usually bound to ThreadLocal. A session uses a SqlSession and needs to close after use

SqlSession Two of the most important parameters in the configuration Same as at initialization, Executor For Executors
public class DefaultSqlSession implements SqlSession {
		private final Configuration configuration;
		private final Executor executor;
j

2.2 Introduction to Executor

Executor is also an interface and has three common implementation classes:

  • BatchExecutor (reuse statements and perform bulk updates)
  • ReuseExecutor (reuse preprocessing statement prepared statements)
  • SimpleExecutor (normal executor, default)
 SqlSession sqlSession = factory.openSession();
List<User> list =
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

 
//6. Enter the o penSession method.
public SqlSession openSession() {
  //getDefaultExecutorType() passes SimpleExecutor
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

    //7. Enter openSessionFromDataSource.
    //ExecutorType is Executor type, TransactionIsolationLevel is transaction isolation level, whether autoCommit opens transaction
    //Multiple overloaded methods of openSession can specify the Executor type of the resulting SeqSession and transaction handling
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // Get Environment Object
            final Environment environment = configuration.getEnvironment();
            // Create Transaction Object
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // Create Executor Object
            final Executor executor = configuration.newExecutor(tx, execType);
            // Create DefaultSqlSession object
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // Close Transaction object if an exception occurs
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

Execute api in sqlsession

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            //Remove the MappedStatement object from the mapped Map based on the incoming fully qualified name + method name
            MappedStatement ms = configuration.getMappedStatement(statement);
            // Execute Query
          	// Call method processing in Executor
         		 //RowBounds are used for logical division
         		 // wrapCollection(parameter) is used to decorate a collection or array parameter
            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();
        }
    }

Source Profiling-executor

    //This method is implemented in the parent BaseExecutor of SimpleExecutor
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //Gets the SQL statement dynamically based on the parameters passed in, and returns the result as a BoundSql object
        BoundSql boundSql = ms.getBoundSql(parameter);
        //Create a cached Key for this query
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // query
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    // Read operation from database
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // In the cache, add a placeholder object. The placeholder here, related to delayed loading, is visible `DeferredLoad#canLoad()`method
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // Perform read operations
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // Remove placeholder from cache
            localCache.removeObject(key);
        }
        // Add to Cache
        localCache.putObject(key, list);
        // Ignore for now, stored procedure related
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // Incoming parameter creates StatementHanlder object to execute query
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // Creating statement objects in jdbc
            stmt = prepareStatement(handler, ms.getStatementLog());
            // Perform StatementHandler, read
            return handler.query(stmt, resultHandler);
        } finally {
            // Close StatementHandler object
            closeStatement(stmt);
        }
    }


    // Initialize StatementHandler object
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // Get Connection Object
        Connection connection = getConnection(statementLog);
        // Create a Statement or PrepareStatement object
        stmt = handler.prepare(connection, transaction.getTimeout());
        // Set parameters on SQL, such as placeholders on PrepareStatement objects
        handler.parameterize(stmt);
        return stmt;
    }

After several transitions in the Executor.query() method, a StatementHandler object is created, and the necessary parameters are passed to it

StatementHandler, which uses StatementHandler to complete a query to the database and ultimately returns a List result set

As you can see from the code above, Executor functions as follows:

Source Profiling-StatementHandler

The StatementHandler object does two main things:

  • For objects of type PreparedStatement of JDBC, how many SQL statement strings do we use during creation? Placeholder, we will set the value of the placeholder later. StatementHandler sets the value of S tatement by parameterize(statement) method;
  • StatementHandler completes execution of Statement by using the List query(Statement statement, ResultHandler resultHandler) method and encapsulates the resultSet returned by the Statement object into a List
public void parameterize(Statement statement) throws SQLException {
        //Setting Statement using the ParameterHandler object
        parameterHandler.setParameters((PreparedStatement) statement);
    }


   public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // Traversing the ParameterMapping array
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // Get the ParameterMapping object
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    // Get value
                    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);
                    }
                    // Get the typeHandler, jdbcType properties
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    // Set up? Placeholder parameters
                    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);
                    }
                }
            }
        }
    }

The implementation of the Statement statement (ResultHandler resultHandler) method of the StatementHandler is a call to the handleResultSets(Statement) method of the ResultSetHandler.

The handleResultSets(Statement) method of ResultSetHandler converts the resultSet result set generated after the Statement statement is executed into a List result set

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // Execute Query
        ps.execute();
        // Processing Return Results
        return resultSetHandler.handleResultSets(ps);
    }
    
    
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // A result set of multiple ResultSets, one Object object for each ResultSet. In fact, each Object is a List<Object>object.
        // Regardless of the multiple ResultSets of stored procedures, a common query is actually a ResultSet, that is, a multipleResults is at most one element.
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // Get the first ResultSet object and encapsulate it as a ResultSetWrapper object
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // Get ResultMap Array
        // Regardless of the multiple ResultSets of stored procedures, a common query is actually a ResultSet, that is, a resultMaps is an element.
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // check
        while (rsw != null && resultMapCount > resultSetCount) {
            // Get ResultMap Objects
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // Processing ResultSet, adding results to multipleResults
            handleResultSet(rsw, resultMap, multipleResults, null);
            // Get the next ResultSet object and encapsulate it as a ResultSetWrapper object
            rsw = getNextResultSet(stmt);
            // Clear
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }

        // Because `mappedStatement.resultSets'is only used in stored procedures, this series is ignored for the moment
        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++;
            }
        }

        // If it is a multipleResults element, the first element returns
        return collapseSingleResultList(multipleResults);
    }

2. Mapper Agent Method

Handling of interfaces during MyBatis initialization: MapperRegistry is a property of Configuration that maintains a HashMap factory class for the mapper interface, one for each interface. The package path of an interface or a specific interface class can be configured in mappers.

Source Profiling-getmapper()

    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // Get MapperProxyFactory object
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        // If it does not exist, throw a BindingException exception
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        ///Generate instances through dynamic agent factories.
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

   //newInstance method in MapperProxyFactory class
    public T newInstance(SqlSession sqlSession) {
        // An implementation class mapperProxy for the invocationHandler interface that creates the JDK dynamic proxy
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // Invoke overloaded method
        return newInstance(mapperProxy);
    }
    protected T newInstance(MapperProxy<T> mapperProxy) {

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

Source Profiling-invoke()

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // If it is an Object-defined method, call it directly
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);

            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // Get MapperMethod Object
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // Here's the point: MapperMethod ultimately calls the method of execution
        return mapperMethod.execute(sqlSession, args);
    }
public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //Determine the type of method in mapper, and ultimately the method in SqlSession
        switch (command.getType()) {
            case INSERT: {
                // Conversion parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                // Perform INSERT operation
                // Convert rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // Conversion parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                // Convert rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // Conversion parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                // Convert rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // If there is no return and there are ResultHandler method parameters, the results of the query are submitted to ResultHandler for processing
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                // Execute the query and return the list
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // Execute the query and return to Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                // Execute the query and return to Cursor
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                // Execute the query and return a single object
                } else {
                    // Conversion parameters
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // Query Single
                    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());
        }
        // A BindingException exception is thrown if the result is null and the return type is base
        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 results
        return result;
    }

Topics: Mybatis