myBatis: sql execution process

Reprinted from the excellent articles of Nanke Meng Brothers: https://www.cnblogs.com/dongying/p/4142476.html

1. 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.

(1) First, 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);
  }

(2) When we get the SqlSessionFactory, we can get the SqlSessionFactory object through 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 {
      //The Confuguration object is used to obtain Mybatis configuration information. The Environment object contains the configuration of data source and transaction.
      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) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

Through the above steps, the SqlSession object is obtained. Looking at the above, let's also recall the Demo we wrote before.

 @Test
    public void test(){
        //1. Read configuration
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream("classpath:mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(inputStream);
        //2. Obtain SqlSession from SqlSession Factory
        SqlSession sqlSession = build.openSession();
        //3. Get mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //4. Execute sql
        User user = mapper.selectByid(1);
        System.out.println(user.toString());
    }

SqlSession is also available. We can call a series of select, insert, update, delete methods in SqlSession to perform CRUD easily.

2. MapperProxy:

 

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:

(1) Obtain from Configuration through SqlSession. The source code is as follows:

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

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

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

(3) Configuration does not want this hot potato, and then throws it to Mapper Registry. Let's look at Mapper Registry. The source code is as follows:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //The key is here.
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

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

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

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

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

        //3. Get mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //4.3. Execute sql
        User user = mapper.selectByid(1);

3. 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:

/**
   * 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:

/**
   * Look at a lot of code, but in fact it is to first determine the CRUD type, and then according to the type to choose which method to execute sqlSession in the end, around, and 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;
  }

Back to SqlSession, let's take a look at SqlSession's CRUD method. 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 is actually handed over to Excetor to handle, excutor is actually only wearing a vest, small sample, don't think I don't know you wearing 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:

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.

Keywords: SQL Mybatis xml Session

Added by Otoom on Tue, 30 Jul 2019 12:54:38 +0300