Suddenly a question came to mind: every time we execute SQL Will create one SqlSession,Then in a transaction, multiple mapper Execute multiple sql,each sql Will be created sqlSession Are you?Run a Demo test
Test whether a SqlSession will be created for each request when no transaction is added to the method:
It can be seen from the log that without adding transactions, Mapper will create a SqlSession to interact with the database every time it requests the database. Let's take a look at the case with transactions:
It can be seen from the log that after adding transactions to the method, only one SqlSession is created in two requests. After each SQL execution, the SqlSession will be released and obtained the next time the SQL is executed. Proves the above answer.
What is SqlSession
We have to understand, what is SqlSession?
Simply put, SqlSession is the top-level API session interface for MyBatis. All database operations are implemented through it. Because it is a session, that is, a SqlSession should only survive in one business request. It can also be said that SqlSession corresponds to this database session. It is not permanent. It needs to be created every time you access the database.
Therefore, SqlSession is not thread safe. Each thread should have its own SqlSession instance. Do not make SqlSession in the form of single instance or static domain and instance variables. These forms will lead to transaction problems in SqlSession, which is why multiple requests share a SqlSession session in the same transaction, Let's illustrate this from the creation process of SqlSession:
- Get the Environment data source from the Configuration class;
- Obtain TransactionFactory and DataSource from the data source, and create a Transaction connection management object;
- Create the Executor object (SqlSession is just the facade of all operations, and the real work is the Executor, which encapsulates all the operation details of the underlying JDBC);
- Create a SqlSession session.
Each time a SqlSession session is created, a connection management object dedicated to the SqlSession will be created. If the SqlSession is shared, transaction problems will occur.
From the perspective of source code
Mapper's implementation class is a proxy. MapperProxy.invoke() is the real logic. The final execution of this method is SqlSessionTemplate.
org.mybatis.spring.SqlSessionTemplate:
private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
This is the construction method of creating sqlsessiontemplate. It can be seen that SqlSession is used in sqlsessiontemplate, which is a dynamic proxy class implemented by SqlSessionInterceptor. Therefore, we go directly to the fortress:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
All Mapper methods will use this invoke method to handle all database operations.
Spring integrates mybatis and uses mybatis alone. The difference is that spring encapsulates all processing details. You don't need to write a lot of redundant code and focus on business development.
The dynamic agent method mainly does the following processing:
- Obtain a SqlSession according to the current conditions. At this time, the SqlSession may be newly created or the SqlSession obtained from the last request;
- The reflection executes the SqlSession method, and then determines whether the current session is a transaction. If it is a transaction, it will not commit;
- If an exception is thrown at this time, judge that if it is a PersistenceExceptionTranslator and is not empty, close the current session and set sqlSession to empty to prevent finally repeated closing. PersistenceExceptionTranslator is the exception interface of the data access integration layer defined by spring;
- F inally, regardless of the execution result, as long as the current session is not empty, the operation of closing the current session will be executed. The operation of closing the current session will determine whether the session is released or closed directly according to whether there are transactions in the current session
org.mybatis.spring.SqlSessionUtils#getSqlSession:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
Did you see the log "Creating a new SqlSession" you saw when you ran a demo? It proves that my direct depth is quite accurate without any error. In this method, the first step is to obtain whether the current thread threadLocal has a SqlSessionHolder from the TransactionSynchronizationManager (hereinafter referred to as the current thread transaction manager). If so, take out the current SqlSession from the SqlSessionHolder. If the current thread threadLocal has no SqlSessionHolder, create a SqlSession from the sessionFactory, The specific creation steps have been described above, and then register the session to the current thread threadLocal.
Let's take a look at the transaction synchronization manager (the structure of the current thread transaction manager):
public abstract class TransactionSynchronizationManager { // ... // Store the transaction resources of the current thread, such as Connection, session, etc private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); // Store the current thread transaction synchronization callback // When there is a transaction, this field will be initialized, that is, the current thread transaction manager will be activated private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); // ... }
This is a current thread transaction manager of spring, which allows the current resources to be stored in the ThreadLocal of the current thread. It can also be seen from the above that SqlSessionHolder is saved in resources.
org.mybatis.spring.SqlSessionUtils#registerSessionHolder:
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; // Judge whether there is a transaction currently if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); // Judge whether the transaction management factory configured in the current environment is SpringManagedTransactionFactory (default) if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); } holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // Bind the current SqlSessionHolder to ThreadLocal thread TransactionSynchronizationManager.bindResource(sessionFactory, holder); // Register SqlSession synchronization callback TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); // Session usage + 1 holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } }
The condition for registering SqlSession to the current thread transaction manager is first that there are transactions in the current environment, otherwise it will not be registered. The condition for judging whether there are transactions is whether the ThreadLocal of synchronizations is empty:
public static boolean isSynchronizationActive() { return (synchronizations.get() != null); }
Whenever we start a transaction, we will call the initSynchronization() method to initialize synchronizations to activate the current thread transaction manager.
public static void initSynchronization() throws IllegalStateException { if (isSynchronizationActive()) { throw new IllegalStateException("Cannot activate transaction synchronization - already active"); } logger.trace("Initializing transaction synchronization"); synchronizations.set(new LinkedHashSet<TransactionSynchronization>()); }
Therefore, when there is a transaction, SqlSession will be registered in the ThreadLocal of the current thread.
Returning to the logic of the SqlSessionInterceptor proxy class, it is found that the following methods should be called to determine whether the session needs to be submitted:
org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional:
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); return (holder != null) && (holder.getSqlSession() == session); }
It depends on whether the current SqlSession is empty and whether the current SqlSession is equal to the SqlSession in ThreadLocal. As analyzed earlier, if there is no transaction, the SqlSession will not be saved to the transaction synchronization manager, that is, if there is no transaction, the session will be committed.
org.mybatis.spring.SqlSessionUtils#closeSqlSession:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Releasing transactional SqlSession [" + session + "]"); } holder.released(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closing non transactional SqlSession [" + session + "]"); } session.close(); } }
Method regardless of the execution result, the session closing logic needs to be executed. The judgment here is also to judge whether there is a transaction. If SqlSession is in a transaction, the number of references is reduced and the session is not really closed. If there is no transaction in the current session, close the session directly.
Although Zhong Tong learned about Mybatis, I fell into the trap of Spring and suddenly found that the whole transaction link is under the control of Spring. Here are some mechanisms of Spring's custom transactions. Among them, the current thread transaction manager is the core and central axis of the whole transaction. When there are transactions, The synchronization of the current thread transaction manager will be initialized, that is, the current thread synchronization manager will be activated. When Mybatis accesses the database, it will first obtain SqlSession from the current thread transaction manager. If it does not exist, it will create a session, and then register the session with the current thread transaction manager. If there is a transaction, the session will not be closed or commit ted