Mybatis [2.2] – some questions about creating sqlsession source code analysis?

Time:2021-5-4

The code is placed directly in GitHub warehouse【 https://github.com/Damaer/Myb… 】It can be run directly, so it doesn’t take up space.

[TOC]

1. Why don’t we need to close the stream when we use SQL session factory builder?

Let’s look at our code:

public class StudentDaoImpl implements IStudentDao {
    private SqlSession sqlSession;
    public void insertStu(Student student) {
        try {
            InputStream inputStream;
            inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            sqlSession=sqlSessionFactory.openSession();
            sqlSession.insert("insertStudent",student);
            sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
    }
}

When we useinputStream = Resources.getResourceAsStream("mybatis.xml");At the same time, we need to close the InputStream. We can look at the source code and see theSqlSessionFactoryBuilder().build()This method:

//The InputStream is passed in, and another build () method is called
    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

Next, let’s look at another build method. There is a finally module in it. No matter what, it will execute the close method. That’s why we don’t need to close the InputStream when we use it: because the stream is closed in the finally code block.

public SqlSessionFactory build(InputStream inputStream, String environment, 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 {
                //Close the flow
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }
        return var5;
    }

2. How is sqlsession created?

Statement inside the execution code: useSQLSessionFactoryGo and open onesessionIt’s heresessionWe can initially understand it as asqlIf we want to send messages to others, we must open a conversation with others.

sqlSession=sqlSessionFactory.openSession();

We need to look at the source code. We find that opensession is an interface method of sqlsessionfactory and sqlsessionfactory is an interface.

public interface SqlSessionFactory {
    //Only one method is pasted here, and the others are not pasted
    SqlSession openSession();
    }

Idea selects the method,ctrl + alt +BWe can find that two classes, defaultsqlsessionfactory and sqlsessionmanager, implement the interface of sqlsessionfactory
Mybatis [2.2] - some questions about creating sqlsession source code analysis?
Then we need to follow the opensession method of the defaultsqlsessionfactory class, which calls an encapsulated method: opensessionfromdatasource ()

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

Of courseDefaultSqlSessionFactoryThere is also a method in this class. The parameter is autocommit, that is, you can specify whether to submit automatically

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

If we follow the source code, we will find that one parameter isautoCommit, that is, automatic submission. We can see that the value passed in the previous step is false, that is, it will not be submitted automatically. We can get the environment through configuration, then open and get a transaction factory through environment, get the transaction object through the transaction factory, and create an executor through the transaction object, Executor is an interface. The implementation classes include simpleexecution, batchexecutor and reuseexecution. Therefore, the executtype in our code below is to specify its type, generate the executor of the specified type, and refer it to the interface object. After having the executor, we can return a defaultsqlsession object.

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        DefaultSqlSession var8;
        try {
            //Configuration is the main configuration file
            Environment environment = this.configuration.getEnvironment();
            //Get the transaction factory, and the transaction manager can make JDBC and so on
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //Get the transaction object transaction
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //Create an executor through a transaction object
            Executor executor = this.configuration.newExecutor(tx, execType);
            //Defaultsqlsession is the sqlsession implementation class. Create a defaultsqlsession and return it
            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;
    }

We’re with you var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);In this code, we assign the initialization function to each member variable. We find that there is a dirty member in it. What is this for? From the name, we understand it as dirty. Since it is set to false, it means not dirty. So what is dirty?Dirty refers to the inconsistency between the data in the memory and the data in the database. If it is consistent, it is not dirty
The role of dirty will be explained later. Here, a sqlsession is created.

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

3. How is the addition, deletion and modification carried out

We use this Code:

sqlSession.insert("insertStudent",student);

We find that it is also an interface method. As we know above, sqlsession is actually the interface implemented by defaultsqlsession. Then we follow the insert () method of defaultsqlsession. We find that the underlying layer of insert method also implements the update method, and the underlying layer of delete method also calls the update method,Adding, deleting and changing are essentially changes

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

In the update method, dirty becomes true, indicating that the data will be changed soon. Therefore, the database data is inconsistent with the data in memory. Statement is the ID we passed through. In this way, we can get the object of statement through ID, and then modify it through the actuator

public int update(String statement, Object parameter) {
        int var4;
        try {
            //Dirty becomes true, indicating that the data and database data are inconsistent and need to be updated
            this.dirty = true;
            //Get the mapping relation from the configuration through the ID of the statement
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //The actuator performs the modified operation
            var4 = this.executor.update(ms, this.wrapCollection(parameter));
        } catch (Exception var8) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
        } finally {
            ErrorContext.instance().reset();
        }
        return var4;
    }

4. Why can sqlsession. Commit() commit a transaction?

First of all, we use the same source code as defaultsqlsession. We find that another commit method is called in commit, passing in a false value

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

We follow in and find that the above passed in false is the variable force, which calls aisCommitOrRollbackRequired(force)Method, the result of execution is returned to the commit method as a parameter.

public void commit(boolean force) {
    try {
        this.executor.commit(this.isCommitOrRollbackRequired(force));
        //After submitting, dirty is set to false because the data in the database and memory are consistent.
        this.dirty = false;
    } catch (Exception var6) {
        throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
    } finally {
        ErrorContext.instance().reset();
    }
}

Let’s go inisCommitOrRollbackRequired(force)This method, this method is namedDo you need to commit or rollbackI mean. Previously, we know that autocommit is false, and then it will be true after negation. As for dirty, we know that we have executed the insert () method before. The underlying layer of insert calls the update method, and sets dirty to true, which means that the data will be modified. So we know!this.autoCommit && this.dirtyIf the value of the expression is true, it will be short circuited, so the value of the whole expression is true.

private boolean isCommitOrRollbackRequired(boolean force) {
    return !this.autoCommit && this.dirty || force;
}

Back to the upper level, we knowthis.isCommitOrRollbackRequired(force)The return value of is true.

this.executor.commit(this.isCommitOrRollbackRequired(force));

Follow the commit method, which is an interface method. There are baseexecutor and cacheingexecution to implement the interface. We choose the interface implementation class baseexecutor

//Required is true
public void commit(boolean required) throws SQLException {
    //If it is closed, there is no way to commit and throw an exception
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        //If required is true, the transaction is committed
        if (required) {
            this.transaction.commit();
        }
    }
}

5. Why does sqlsession close without rollback?

If we have submitted above, then the value of dirty is false. We usesqlSession.close();The source code is the same as the interface. We call iscommitorrollbackrequired () as the method of defaultsqlsession

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

We follow the iscommitorrollbackrequired (false) method. We know that the value passed in by force is false and autocommit is false (as long as we use parameterless)sqlSessionFactory.openSession();), after negation!autoCommitIt’s true, but dirty is already false, so!this.autoCommit && this.dirtyIf the value of is false, then force is also false, so the whole expression is false:

    private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

Let’s go back to the previous layer, the executor. Close() method. The parameter is false:

this.executor.close(this.isCommitOrRollbackRequired(false));

Following the close () method, the value of forcerollback is false. We find that there is onethis.rollback(forceRollback)

public void close(boolean forceRollback) {
        try {
            try {
                this.rollback(forceRollback);
            } finally {
                //Finally, if the transaction is not empty, we close the transaction
                if (this.transaction != null) {
                    this.transaction.close();
                }
            }
        } catch (SQLException var11) {
            log.warn("Unexpected exception on closing transaction.  Cause: " + var11);
        } finally {
            this.transaction = null;
            this.deferredLoads = null;
            this.localCache = null;
            this.localOutputParameterCache = null;
            this.closed = true;
        }
    }

If we follow the method of rollback (), we can find that required is a fast this.transaction.rollback();It will not be executed. Because we submitted it earlier, we do not need to roll back it

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

            }
        }

    }

If we finish executing the insert () method now, but do not use commit (), then the current dirty is true, that is, the database data is inconsistent with the memory data. When we execute the close () method again, dirty is true,! If this. Autocommit is true, then the entire expression is true.

    private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

Return to the previous level, and the parameter of close will become true

this.executor.close(this.isCommitOrRollbackRequired(false));

The close () method calls the this.rollback(forceRollback);, and the parameter is true. We can follow and see that the rollback is actually performed

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

            }
        }

    }

So as long as we commit, rollback will not be performed when closing. As long as the transaction is not committed, rollback will occur. Therefore, dirty is very important.

[about the author]
Qin Huai, official account.Qinhuai grocery store】The author points out that the road of technology is not for a moment, even though it is slow and continuous. The world hopes that everything will be fast and faster, but I hope I can take every step and write every article well. I look forward to communicating with you.

This article is only on behalf of their own (this rookie) learning accumulation records, or learning notes, if there is infringement, please contact the author to verify the deletion. No one is perfect, so is the article. The style of writing is immature. If you don’t have talent, don’t spray. If you have any mistakes, I hope you can point them out. Thank you very much~