MyBatis Series Talking about SQL Execution Process Analysis

Keywords: Mybatis SQL xml Database

Catalog

Use Mybatis on its own

This article focuses on analyzing the Mybatis framework's processes for executing SQL.
Recalling that we used the Mybatis semi-automation framework on our own, we needed to follow these steps:

  1. Read the configuration file (mybatis-config.xml) and initialize the configuration class, configuration;
  2. Create SQLSessionFactory;
  3. Create SqlSession;
  4. Execute SQL, process result set
    Corresponds to the following code:
public class MyBatisUtils {
    private final static SqlSessionFactory SQL_SESSION_FACTORY;
    static {
        String resource = "mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader);
    }
    public static SqlSessionFactory getSqlSessionFactory() {
        return SQL_SESSION_FACTORY;
    }
}

//unit testing
public class MapperTest {
    static SqlSessionFactory sqlSessionFactory = null;
    static {
        sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
    }

    @Test
    public void testAdd() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = new User();
            user.setUsername("hello");
            user.setAge(5);
            userMapper.insert(user);
            sqlSession.commit();    // Be sure to submit here, or the data won't go into the database
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void getUser() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.selectByPrimaryKey(32L);
            System.out.println("name: " + user.getUsername() + "| age: " + user.getAge());
        } finally {
        }
    }
}

Mybatis Execution Process

Based on the above instructions, we have a basic understanding of the execution process, below we will explain the process from the source level.

SqlSessionFactory\SqlSession


When using Mybatis on its own, the first step is to read the configuration file, so we'll start by reading the configuration file.

  1. SqlSessionFactoryBuilder reads mybatis configuration file and calls build method to create DefaultSqlSessionFactory
 /**Create SQLSessionFactory object by parsing mybatis configuration with XMLConfigBuilder*/
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //The parse() method XMLConfigBuilder class parses the xml file and completes the creation of the configuration property
            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.
            }
        }
    }

    /**
     * The build() method above calls this method by default, creating a DefaultSqlSessionFactory object
     * After parsing the mybatis configuration file xml, generate the Configuration object, then generate the SQLSessionFactory object
     * @param config
     * @return
     */
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

  1. After getting the SqlsessionFactory, call the openSession() method to create the SQLSession, which illustrates that SQLSession only provides support for database operations outward. It is execute's duty to actually perform operations on the database.
DefaultSqlSessionFactory class
/**
   * This method is typically invoked by a series of openSession methods
   * @param execType
   * @param level
   * @param autoCommit
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //Get Mybatis configuration information through the Confuguration object, which contains configuration of data sources and transactions
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //Previously, on the surface, we use sqlSession to execute sql statements. Actually, it is executed through excutor, which is the encapsulation of Statement.
      final Executor executor = configuration.newExecutor(tx, execType);
      //Create SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // may have fetched a connection so lets call close()
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

After obtaining the SQLSession, the preparation for the CRUD operation on the data is formally completed.

MapperProxy


Mainly handles our Mybatis mapping file.This class is primarily responsible for mapper s in proxy development.So think about how this proxy object is obtained?
Below we trace from SQLSession.

DefaultSQLSession In class
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }


/**
     * Get the proxy object for the Mapper interface
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //Get the proxy factory object for the Mapper interface from the cache
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        //Throw an exception if the Mapper interface is not registered
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            //**Use proxy factories to create proxy objects for Mapper interfaces**Key Parts
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

MapperProxyFactory class
 /**
     * Create a proxy object for mapperInterface
     */
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

Along the way, we found that the MapperProxy object was created in MapperProxyFactory.

Excutor


We mentioned above that Excutor is responsible for crud operations on data, and the time series diagram above details the execution of SQL.
For each MapperProxy that corresponds to a developer's own Mapper(dao) interface, we'll track how this is done from the source code.

  • MappProxy:
 /**The method executed by the proxy object that will be called after the proxy by all Mapper method calls*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //To determine if it is a generic class, mybatis uses the JDK proxy method, which is interface-oriented, false
            //method.getDeclaringClass() returns the class of the underlying class object
            if (Object.class.equals(method.getDeclaringClass())) {
                //If it's a class, use the reflection mechanism to return the target object
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                //If it is the default method, the default method is executed, provided by Java 1.8;
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        //Gets a MapperMethod object from the cache, creates one if it does not exist in the cache, and adds it to the cache
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //Execute the corresponding SQL statement to return the results of the query
        /**
         * 1,First determine the method type of SQL, insert, select, update
         * 2,map object that converts a parameter name to a relationship between the value of the parameter list
         * 3,Query the result list based on the relationship between the method name and the parameter list
         */
        return mapperMethod.execute(sqlSession, args);
    }
  • MapperMethod:
/**
     * Core method for executing corresponding SQL statements
     * @param sqlSession
     * @param args
     * @return
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                //param is the corresponding relationship between the parameter name and the parameter value, that is, the map set
                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()) {
                    //void type and method with ResultHandler parameter (special parameter), call sqlSession.select to execute
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    //The return type is either a collection or an array and is executed by calling sqlSession. <E>selectList
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    //Return to map, call sqlSession. <K, V>selectMap
                    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;
    }

This article mainly analyses the execution process of SQL, so we recommend taking selectList as an example to familiarize you with the process, which will be introduced later
The process of each step.
Return to SQLSession

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }
  • BaseExecutor:
/**
     * Query methods, which specifically provide methods for select execution
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //Get Query SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        //Create the cached key, which is the key in HashMap
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //Execute Query
        return query(ms, parameter, 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 {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  • preparedStatement:
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

Reference: Spring Source Depth Resolution 2nd Edition

Posted by BradZynda on Fri, 15 May 2020 17:17:44 -0700