This is the 9th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

We have written a tool class to obtain SqlSession for the convenience of the previous, we will analyze the execution process of Mybatis today

String resource = "mybatsi-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student studentById = mapper.findStudentById(38);
System.out.println(studentById);
sqlSession.close();
Copy the code

This is the code for us to query student information according to ID. Through the code, we find that the execution process of Myabtis is

  1. Get Mybatis master profile
  2. The SqlSessionFactory object is built by passing the SqlSessionFactoryBuilder().build() method into the main configuration file
  3. The SqlSessionFactory object gets the SqlSession object from openSession()
  4. The sqlsession.getMapper () method gets our XML file and gets the Mapper object to execute our SQL return object

How exactly do we just implement this? Let’s look at the source code and see what we do

Mybatis components

SqlSessionFactoryBuilder

Through SqlSessionFactoryBuilder the builder reads the Mybatis master configuration file stream of inputStream through XMLConfigBuilder(). Xmlconfigbuilder.parse () parses the master configuration file and returns a Co Nfiguration () object, calling a build method returns a SqlSessionFactory() object, with two arguments SqlSessionFactory and Properties, environment determines which environment to load. This includes data sources and transaction managers. For example, our project has a development environment, a UAT test environment, and multiple environments in the configuration file. Properties is used to load some properties.

We return an SqlSessionFactory() object after we finish above

public SqlSessionFactory build(InputStream inputStream, String SqlSessionFactory, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
        }

    }

    return var5;
}

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

SqlSessionFactory

With SqlSessionFactory, we can retrieve the SqlSession object from it. The SqlSessionFactory overload can be used by multiple openSession() methods. Our default openSession() method takes no arguments and creates an SqlSession object with the following properties

  1. autoCommitAutomatic submission or not
  2. ConnectionCreating a database session
  3. TransactionIsolationLevelTransaction isolation level
  4. ExecutorTypeActuator type

There are three types of executortypes

  1. SIMPLEThis type of actuator has no particular behavior. It creates a new preprocessed statement for each statement execution
  2. REUSEMultiplexed preprocessed statement
  3. BATCHThis type of executor executes all update statements in batches
public enum ExecutorType {
    SIMPLE,
    REUSE,
    BATCH;

    private ExecutorType() {
    }
}
Copy the code

There is also a getConfiguration method that gets the Configuration object, which is the data after parsing the < Configuration tag in the XML Configuration file

public interface SqlSessionFactory {
    SqlSession openSession();

    SqlSession openSession(boolean var1);

    SqlSession openSession(Connection var1);

    SqlSession openSession(TransactionIsolationLevel var1);

    SqlSession openSession(ExecutorType var1);

    SqlSession openSession(ExecutorType var1, boolean var2);

    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);

    SqlSession openSession(ExecutorType var1, Connection var2);

    Configuration getConfiguration();
}
Copy the code

Look I interface we again to see the implementation class SqlSessionFactory DefaultSqlSessionFactory () every openSession method to invoke openSessionFromDataSource to return SqlSession objects

  1. Get the environment package database configuration and transaction information from our master configuration file.
  2. Create the TransactionFactory TransactionFactory, call the newTransaction method to pass in the database information, isolation level, and whether to commit automatically.
  3. Create a new constructor executor passing in the transaction, constructor type
  4. Return a DefaultSqlSession object

At this point, we get a SqlSession object, and our SQL statements are executed for us by executors. Excutor encapsulates the Statement

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        Executor executor = this.configuration.newExecutor(tx, execType);
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}
Copy the code

SqlSession

SqlSession is one of Mybatis most powerful classes, DefaultSqlSession getMapper() to generate a proxy object for Mapper

SqlSessionManager.java

public <T> T getMapper(Class<T> type) {
    return this.getConfiguration().getMapper(type, this);
}
Copy the code

Configuration.java

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

MapperRegistry.java

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 { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); }}}Copy the code

Mapperproxyfactory.java dynamically proxies our interface

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);
}
Copy the code

After this operation, we get the proxy object to execute our method, which is the MapperProxy Invoik method

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }

        if (method.isDefault()) {
            return this.invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    return mapperMethod.execute(this.sqlSession, args);
}
Copy the code

Call mapperMethod.execute(this.sqlSession, args) to determine what type of SQL is returned to sqlSession

public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || ! this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && ! this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; }}Copy the code

Now how do we implement getList

<E> List<E> selectList(String var1);
Copy the code
public <E> List<E> selectList(String statement) {
    return this.selectList(statement, (Object)null);
}
Copy the code

The.executor.query method called here. You can see that there are two implementations

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

Go to the Query method of CachingExecutor, get it from the level 2 cache first, if not, go to another method to get it from the level 1 cache, if not

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
    return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
            this.clearLocalCache();
        }

        List list;
        try {
            ++this.queryStack;
            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
            if (list != null) {
                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

        if (this.queryStack == 0) {
            Iterator var8 = this.deferredLoads.iterator();

            while(var8.hasNext()) {
                BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                deferredLoad.load();
            }

            this.deferredLoads.clear();
            if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                this.clearLocalCache();
            }
        }

        return list;
    }
}
Copy the code

If not, use the queryFromDatabase method to query the data in the database

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        this.localCache.removeObject(key);
    }

    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}
Copy the code

There are four implementations of the DoQuery method. Let’s look at the DoQuery method of SimpleExecutor

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}
Copy the code

The StatementHandler query method is used to execute the SQL statement

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = this.boundSql.getSql();
    statement.execute(sql);
    return this.resultSetHandler.handleResultSets(statement);
}
Copy the code