In the last article, we explained the MyBatis startup process, and components involved in the process of start, in this paper, we continue to explore SqlSession, SqlSessionFactory, the relationship between the SqlSessionFactoryBuilder is. SqlSession as the core component of MyBatis, it can be said that all operations of MyBatis are launched around SqlSession. A thorough understanding of SqlSession can fully master MyBatis.

series

MyBatis principle series (a)- hand handle with you to read MyBatis source code MyBatis principle series (two)- hand handle with you to understand MyBatis startup process MyBatis principle series (3) – take you understand the SqlSession, holding SqlSessionFactory, principle of the relationship between the SqlSessionFactoryBuilder is MyBatis series (4) – handy belt you know MyBatis Executor of the actuator MyBatis principles series (6)- Learn how to create a BoundSql Statement, StatementHandler, and mappedsql MyBatis principle series (9)- MyBatis principle series (9)- MyBatis with your understanding of the transaction management mechanism

1. The SqlSession first meeting

SqlSession is a high-level interface, similar to the JDBC connection object, which wraps the database connection. Through this interface, we can add, delete, change, check, commit/roll back things, close connections, get proxy classes, and so on. SqlSession is an interface whose default implementation is DefaultSqlSession. SqlSession is not thread safe. Each thread has its own unique SqlSession. Calling the same SqlSession between different threads may cause problems, so you need to close it after using it.

2. Create SqlSession

The build() method of the SqlSessionFactoryBuilder creates the SqlSessionFactory interface object using the builder pattern. The default implementation of the SqlSessionFactory interface is DefaultSqlSessionFactory. SqlSessionFactory uses the instance factory pattern to create the SqlSession object. SqlSession, SqlSessionFactory, the relationship between the SqlSessionFactoryBuilder is as follows (picture a bit ugly…). :

DefaultSqlSessionFactory openSession is there are two ways one is openSessionFromDataSource, another kind is openSessionFromConnection. What’s the difference between the two? Literally, there is one way to get a SqlSession object from a data source, or one way to get a SqlSession from an existing connection. SqlSession is actually a wrapper around database connections. Database connections are a valuable resource. Frequent creation and destruction will affect throughput, so database connection pooling can be used to reuse database connections. So openSessionFromDataSource will obtain a connection from the database connection pool, and then packaged into a SqlSession to like. OpenSessionFromConnection is an existing connection directly packing and return SqlSession to like.

OpenSessionFromDataSource mainly experienced the following steps:

  1. Get the Environment object from get Configuration, which contains the database configuration
  2. Get the DataSource DataSource from Environment
  3. Gets the Connection object from the DataSource DataSource
  4. Get the TransactionFactory TransactionFactory from the DataSource DataSource
  5. Create Transaction objects from TransactionFactory
  6. Create an Executor object
  7. Wrap the Configuration and Executor objects as DefaultSqlSession objects
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      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(); }}private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
    } finally{ ErrorContext.instance().reset(); }}Copy the code

3. Use SqlSession

After SqlSession is successfully obtained, we can use one of the methods, such as directly using SqlSession to send SQL statements, or through mapper mapping file to use, in the last two articles we are using mapper mapping file to use, next we will introduce the first method, Send SQL statements directly using SqlSession.

public static void main(String[] args){
        try {
            1. Read the configuration
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // get the SqlSessionFactory object
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 3. Obtain the SqlSession object
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 4. Execute SQL
            TTestUser user = sqlSession.selectOne("com.example.demo.dao.TTestUserMapper.selectByPrimaryKey".13L);
            log.info("user = [{}]", JSONUtil.toJsonStr(user));
            5. Close the connection
            sqlSession.close();
            inputStream.close();
        } catch (Exception e){
            log.error("errMsg = [{}]", e.getMessage(), e); }}Copy the code

The com. Example. Demo. Dao. TTestUserMapper. SelectByPrimaryKey specifies the TTestUserMapper selectByPrimaryKey in this method, The corresponding mapper/TTestUserMapper. XML we defined the id consistent SQL statements

  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_test_user
    where id = #{id,jdbcType=BIGINT}
  </select>
Copy the code

Mybatis will wrap the SQL statement in each tag into MappedStatement object at the beginning of loading, and cache it in memory with the class full path name + method name key and MappedStatement as value. When the corresponding method is executed, the SQL statement tTestUsermapper.xml is found based on this unique path and the result is returned.

4. Execution principle of SqlSession

4. 1 Implementation principle of selectOne of SqlSession

The selectOne code for SqlSession is as follows, which actually calls the selectList() method to get the first data. The statement parameter is the ID of the statement, and parameter is the parameter.

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null; }}Copy the code

The RowBounds object is a paging object that concatenates start and limit conditions in SQL. And you can see two important steps:

  1. Get the MappedStatement object from the mappedStatements member variable of the Configuration. MappedStatements are cache structures of type Map

    , where key is the mapper interface class name + method name, and MappedStatement is a wrapper around the SQL configured in the tag
    ,>
  2. Use executor member variables to execute queries and specify result handlers, and return results. Executor is also an important component of MyBatis. SQL execution is handled by Executor objects.
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }
Copy the code

The details of the MappedStatement object and the type of the Executor object will be detailed in other articles.

4. 2 Execution principle of SqlSession using mapper objects

Sqlsession. getMapper returns a proxy class called MapperProxy, and calls MapperProxy’s invoke method. The execute method of MapperMethod is then called.

public static void main(String[] args) {
       try {
           1. Read the configuration
           InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
           // create SqlSessionFactory
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           // 3. Obtain the sqlSession
           SqlSession sqlSession = sqlSessionFactory.openSession();
           // 4. Get Mapper
           TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
           // 5. Execute the interface method
           TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
           System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
           // 6. Submit something
           sqlSession.commit();
           // 7. Close resources
           sqlSession.close();
           inputStream.close();
       } catch(Exception e){ log.error(e.getMessage(), e); }}Copy the code

In the execute method of MapperMethod, the command mode is used to perform add, delete, change, and query operations. In fact, the execute method invokes the add, delete, change, and query method of sqlSession.

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        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()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          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;
  }
Copy the code

5. To summarize

In this article, we detail the role of SqlSession, the creation process, the use of the method, as well as the execution principle, etc., SqlSession has a more comprehensive understanding. The Executor objects involved, the MappedStatement object, the ResultHandler we’ll talk about in another article. Feel free to discuss corrections and progress together in the comments section.