Analysis of Mybatis Series 10-SQL Execution Flow (Source Code)

Note: This article is reproduced from Nan Ke Meng

Recently, I have been too busy to update my blog. Today, I am busy to continue my Mybatis study tour. In the first nine chapters, the configuration and use of mybatis are introduced. Then this chapter will go into the source code of mybatis and analyze the execution process of mybatis.

SqlSession Factory and SqlSession

Through the introduction and use of mybatis in the previous chapters, we can all appreciate the importance of SqlSession. Yes, on the surface, we all use SqlSession to execute sql statements. Let's see how to get SqlSession first.

First, the SqlSession Factory Builder reads the configuration file of mybatis, and then build s a Default SqlSession Factory. The source code is as follows:

/**
* A series of construction methods will eventually call this method (when the configuration file is Reader, this method will be called, and an InputStream method corresponds to this)
* @param reader
* @param environment
* @param properties
* @return
*/
public SqlSessionFactory build(Reader reader,String environment,Properties properties){
    try {
      //By parsing the configuration file with XML ConfigBuilder, the parsed configuration-related information is encapsulated as a Configuration object.
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //Create DefaultSessionFactory objects here
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

When we get the SqlSessionFactory, we can get the SqlSession object through the SqlSessionFactory. The source code is as follows:

/**
* Usually a series of openSession methods will eventually call this method
* @param execType 
* @param level
* @param autoCommit
* @return
*/
private SqlSession openSessionFromDataSource(ExecutorType execType
        , TransactionIsolationLevel level
        , boolean autoCommit) {
    Transaction tx = null;
    try {
      //Get Mybatis-related configuration through the Confuguration object, which contains the configuration of data sources and transactions
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory 
        = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource()
                         , level
                         , autoCommit);

      /**
      * As I said before, 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);
      //The key here is to create a DefaultSqlSession object
      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();
    }
}

Through the above steps, we have got the SqlSession object. Next is what to do (what else to do, of course, execute sql statements). Looking at the above, let's also recall the Demo we wrote before.

SqlSessionFactory sessionFactory = null;  
String resource = "mybatis-conf.xml";  
try {
     //SqlSession FactoryBuilder reads configuration files
    sessionFactory = new SqlSessionFactoryBuilder().build(Resources  
              .getResourceAsReader(resource));
} catch (IOException e) {  
    e.printStackTrace();  
}    
//Getting SqlSession through SqlSession Factory
SqlSession sqlSession = sessionFactory.openSession();

That's true, isn't it?

SqlSession is also available. We can call a series of select, insert, update, delete methods in SqlSession to perform CRUD easily. That's it. Where is the mapping file we configured? Don't worry. Let's go on and see:

Mapper Proxy

In mybatis, we dynamically proxy our Dao through MapperProxy, that is to say, when we execute the method in our own dao, we are actually proxying the corresponding mapperProxy. So let's see how to get MapperProxy objects:

Get it from Configuration through SqlSession. The source code is as follows:

/**
* Don't do anything, go straight to the configuration, brother is so capricious.
*/
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

SqlSession dumped the burden on Configuration, and then looked at Configuration. The source code is as follows:

/**
* Hot potato, I don't want it. You go to mapper Registry and ask for it.
* @param type
* @param sqlSession
* @return
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

Configuration doesn't want this hot potato, and then throws it to Mapper Registry. Let's take a look at Mapper Registry. The source code is as follows:

/**
* Lousy work let me do it, no way, no one below, I do not do who to do it.
* @param type
* @param sqlSession
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //If you can be lazy, you can be lazy. I leave the rough work to Mapper ProxyFactory.
    final MapperProxyFactory<T> mapperProxyFactory 
        = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " 
        is not known to the MapperRegistry.");
    }
    try {
      //The key is here.
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance.Cause: " 
        + e, e);
    }
 }

Mapper ProxyFactory is a bitter B person, and the rough work is finally left to it. Let's look at the source code:

/**
* People abuse me thousands of times, I treat others like first love
* @param mapperProxy
* @return
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    //Dynamic proxy dao interface we write
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader()
        , new Class[] { mapperInterface }, mapperProxy);
}
  
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy 
        = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

Through the above dynamic proxy, we can easily use the dao interface, as we wrote demo before:

UserDao userMapper = sqlSession.getMapper(UserDao.class);  
User insertUser = new User();

This is more convenient, ha ha, it seems that the source code of mybatis is the same thing.

Don't worry. It's not over yet. We haven't seen how to execute sql statements.

Excutor

Next, let's really look at the execution of sql.

Above, we got MapperProxy. Each MapperProxy corresponds to a dao interface. So what does MapperProxy do when we use it? Source code enclosed:

MapperProxy source code

/**
* MapperProxy This method is triggered at execution time
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //Let's leave it to MapperMethod.
    return mapperMethod.execute(sqlSession, args);
}

MapperMethod source code

/**
* There's a lot of code to look at, but it's really about judging the CRUD type first, and then choosing which method to execute in sqlSession according to the type.
* After a lap, I went back to sqlSession.
* @param sqlSession
* @param args
* @return
*/
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
}

Now that we are back to SqlSession, let's take a look at the CRUD method of SqlSession. In order to save time, we should choose one of the methods for analysis. Here, we choose the selectList method:

public <E> List<E> selectList(String statement
                  , Object parameter
                  , RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      /**
      * CRUD It's actually handed over to Excetor. Exceutor is just wearing a vest.
      * Sample, don't think I don't know you in a vest!
      */
      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();
    }
}

Then, through layer-by-layer invocation, we will eventually come to the doQuery method. Let's find an Excutor to see the implementation of the doQuery method. Here I choose SimpleExecutor:

SimpleExecutor

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());
      //StatementHandler encapsulates Statement and lets StatementHandler process it.
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

Next, let's look at an implementation class of StatementHandler, PreparedStatementHandler (which is also our most commonly used, encapsulated PreparedStatement), and see how it handles:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) 
    throws SQLException {
    //So far, the original shape is revealed, Prepared Statement. Everyone's already ripe.
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //The result was handed over to ResultSetHandler for processing.
    return resultSetHandler.<E> handleResultSets(ps);
}

At this point, a sql execution process is over. I'm just trying to get started here, suggesting that you be interested in looking at the source code of Mybatis3.

Well, that's the end of this time.

Keywords: PHP Mybatis SQL xml Session

Added by dicky18 on Sat, 25 May 2019 01:18:48 +0300