MyBatis SqlSession Creation Source Code Analysis and Key Questions

Why InputStream Doesn't Need Manual Closure When Using SqlSessionFactoryBuilder?

Consider this typical usage pattern:

public class StudentRepositoryImpl implements IStudentRepository {
    private SqlSession session;
    
    public void addStudent(Student student) {
        try {
            InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
            session = factory.openSession();
            session.insert("insertStudentRecord", student);
            session.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }
}

When using Resources.getResourceAsStream("mybatis-config.xml"), manual stream closure isn't required. Examining the source code reveals why.

Looking at the SqlSessionFactoryBuilder().build() method:

public SqlSessionFactory build(InputStream inputStream) {
    return this.build(inputStream, null, null);
}

The underlying method includes a finally block that ensures stream closure regardless of execution path:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory result;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        result = this.build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Silent failure
        }
    }
    return result;
}

How SqlSession Objects Are Created

The core creation happens when calling openSession() on SqlSessionFactory:

session = factory.openSession();

Since SqlSessionFactory is an interface, we need to examine its implementations. The primary implementation is DefaultSqlSessionFactory.

The openSession method delegates to a helper method:

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), null, false);
}

There's also an overloaded version that accepts auto-commit configuration:

public SqlSession openSession(boolean autoCommit) {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), null, autoCommit);
}

The actual session creation occurs in openSessionFromDataSource():

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    DefaultSqlSession result;
    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);
        result = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception e) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
    return result;
}

The DefaultSqlSession constructor initializes various fields including a modified flag:

public DefaultSqlSession(Configuration config, Executor exec, boolean autoCommit) {
    this.configuration = config;
    this.executor = exec;
    this.modified = false;  // Tracks whether data has been modified
    this.autoCommit = autoCommit;
}

The modified flag indicates whether in-memory data differs from database data.

How Insert, Update, and Delete Operations Execute

Consider the insertion call:

session.insert("insertStudentRecord", student);

Examining DefaultSqlSession's implementation shows that insert ultimately calls update:

public int insert(String statement, Object parameter) {
    return this.update(statement, parameter);
}

public int update(String statement) {
    return this.update(statement, null);
}

The update method marks data as modified and delegates to the executor:

public int update(String statement, Object parameter) {
    int rowsAffected;
    try {
        this.modified = true;  // Mark that data will be changed
        MappedStatement mappedStmt = this.configuration.getMappedStatement(statement);
        rowsAffected = this.executor.update(mappedStmt, this.wrapCollection(parameter));
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
    return rowsAffected;
}

Why SqlSession.commit() Commits Transactions

The commit operation in DefaultSqlSession calls an overloaded version:

public void commit() {
    this.commit(false);
}

The overloaded method checks commit requirements and updates the modified flag:

public void commit(boolean force) {
    try {
        this.executor.commit(this.shouldCommitOrRollback(force));
        this.modified = false;  // Data consistency restored
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

The decision logic is in shouldCommitOrRollback():

private boolean shouldCommitOrRollback(boolean force) {
    return !this.autoCommit && this.modified || force;
}

The executor's commit method handles the actual transaction commit:

public void commit(boolean required) throws SQLException {
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        if (required) {
            this.transaction.commit();
        }
    }
}

Why Closing SqlSession Doesn't Require Rollback After Commit

When closing the session, the system determines whether rollback is necessary:

public void close() {
    try {
        this.executor.close(this.shouldCommitOrRollback(false));
        this.modified = false;
    } finally {
        ErrorContext.instance().reset();
    }
}

After a successful commit, modified becomes false. The shouldCommitOrRollback(false) evaluation returns false since both !this.autoCommit && this.modified and force are false.

The executor's close method conditionally performs rollback:

public void close(boolean forceRollback) {
    try {
        try {
            this.rollback(forceRollback);
        } finally {
            if (this.transaction != null) {
                this.transaction.close();
            }
        }
    } catch (SQLException e) {
        log.warn("Unexpected exception on closing transaction. Cause: " + e);
    } finally {
        this.transaction = null;
        this.deferredLoads = null;
        this.localCache = null;
        this.localOutputParameterCache = null;
        this.closed = true;
    }
}

The rollback method only executes when required:

public void rollback(boolean required) throws SQLException {
    if (!this.closed) {
        try {
            this.clearLocalCache();
            this.flushStatements(true);
        } finally {
            if (required) {
                this.transaction.rollback();
            }
        }
    }
}

If operations execute without explicit commit, modified remains true, triggering rollback during closure since !this.autoCommit && this.modified evaluates to true.

Tags: MyBatis sqlsession transaction-management source-code-analysis database

Posted on Mon, 26 Oct 2026 16:24:50 +0000 by PHP-Editors.com