Article starts from blog.csdn.net/doujinlong1…

In Spring, the DAO layer is mostly used by Mybatis, so

1, What is the most important thing for Mybatis to execute SQL?

In the previous source code interpretation of Mybatis, we know that Mybatis uses dynamic proxy to do, the final implementation of the class is MapperProxy, in the final implementation of the specific method, in fact, the implementation is:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
Copy the code

The most important step:

mapperMethod.execute(sqlSession, args);
Copy the code

The sqlSessionTemplate class can be used to execute specific SQL queries, such as:

@Override
  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.<T> selectOne(statement, parameter);
  }
Copy the code

So this is the last step, so the question is what exactly is sqlSessionProxy? It goes back to the beginning.

2, use mybatis are generally need to inject for connection to the mysql SqlSessionFactory, SqlSessionTemplate, PlatformTransactionManager.

The SqlSessionTemplate template is used to generate a sqlSession.

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	return new SqlSessionTemplate(sqlSessionFactory);
}
Copy the code

During this initialization:

/** * Constructs a Spring managed {@code SqlSession} with the given * {@code SqlSessionFactory} and {@code ExecutorType}. * A custom {@code SQLExceptionTranslator} can be provided as an * argument so any {@code PersistenceException} thrown by MyBatis * can be custom translated to a {@code RuntimeException} * The {@code SQLExceptionTranslator} can also be null and thus no * exception translation will be done and MyBatis exceptions will be  * thrown * * @param sqlSessionFactory * @param executorType * @param exceptionTranslator */ 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());Copy the code

}

The last step, which is important, is to generate an sqlSessionFactory using the Java dynamic proxy. The proxy class is:

/** * Proxy needed to route MyBatis method calls to the proper SqlSession got * from Spring's Transaction Manager * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...) } to * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}. */ 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); }}}Copy the code

}

This proxy class is used when SQL is executed in sqlSession. IsSqlSessionTransactional this will determine whether have Transactional, not directly submit. If yes, do not commit and commit in the outermost layer.

Among them

getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exc eptionTranslator);Copy the code

This method is used to get the sqlSession. The concrete implementation is as follows:

/** * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. * Tries to get a SqlSession out  of current transaction. If there is not any, it creates a new one. * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and * <code>SpringManagedTransactionFactory</code> is configured as a transaction manager. * * @param sessionFactory a MyBatis  {@code SqlSessionFactory} to create new sessions * @param executorType The executor type of the SqlSession to create * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions. * @throws TransientDataAccessResourceException if a transaction is active and the * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory} * @see SpringManagedTransactionFactory */ 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; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }Copy the code

Get a sqlSession from the Spring transaction manager, if not, create a new one. If not, create a new one. ** Spring gives you a sqlSession if you are in a transaction, otherwise, every SQL gives you a new sqlSession. ** sqlSession is DefaultSqlSession. There may still be agents, such as the Mybatis paging plug-in, that are beyond the scope of this discussion.

3, the second step of the sqlSession is not the same, what is the impact?

In 2, we saw that if it is a transaction, the sqlSession is the same, if it is not, it is different every time and committed every time. That’s the most important thing.

SqlSession, as the name implies, is a SESSION in SQL. What happens in this session does not affect other sessions. If the session is committed, it takes effect, and if not, it does not take effect.

Take a look at the sqlSession interface.

/** * The primary Java interface for working with MyBatis. * Through this interface you can execute commands, Get mappers and manage Transactions. * The most important Java interface to work with Mybatis to execute commands, get mapper and manage transactions * @author Clinton Begin */Copy the code

The comments are clear. Let’s see how they work.

3.1. Execute commands.

The most important method to execute SQL in the first subheading is this.sqlsessionProxy.selectOne (Statement, parameter); This method, as we saw in the second subheading, is executed by proxy and ends up committing SQL without actually having a transaction. This is the basic action of executing SQL. Obtain the SQLSession and submit the Sql for execution.

3.2. Obtain mapper.

We might not do this in our everyday code, but we could do it if we had to, for example:

XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class);
Copy the code

Normally, if you want to do this, you need to inject sqlSessionFactory first and then use

SqlSessionFactory. OpenSession ().Copy the code

Get session.

####3.3, transaction Management ####

SqlSession that agent class has an operation, determine whether this is transaction management sqlSession, if yes, then do not submit, not just submit, this is transaction management, then have a question, where to submit this transaction ????

4. Commit from where transactions are intercepted

In Spring, a method marked Transactional by the @transactional annotation will eventually be proffered by the TransactionInterceptor class if it works (see my blog on dynamic proxy.transactional). This method actually does something like this:

@Override public Object invoke(final MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<? > targetClass = (invocation.getThis() ! = null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { @Override public Object proceedWithInvocation() throws Throwable { return invocation.proceed(); }}); }Copy the code

Continue with the invokeWithinTransaction method:

/** * General delegate for around-advice-based subclasses, delegating to several other template * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} * as well as regular {@link PlatformTransactionManager} implementations. *  @param method the Method being invoked * @param targetClass the target class that we're invoking the method on * @param  invocation the callback to use for proceeding with the target invocation * @return the return value of the method, if any * @throws Throwable propagated from the target invocation */ protected Object invokeWithinTransaction(Method method, Class<? > targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); / / we basically the transaction manager is not a CallbackPreferringPlatformTransactionManager, so basically will enter, from this place else situation will not enter the discussion below. if (txAttr == null || ! (tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction And commit/rollback calls. // Get the specific TransactionInfo. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); Execute the specific method in the @transactional annotation. } Catch (Throwable ex) {// Target Invocation exception // The transaction is not committed at the end of the SQLSession, so the transaction is rolled back. completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } // we can see what is actually being done :(commit of programmatic transaction) // ==============txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); =========== commitTransactionAfterReturning(txInfo); return retVal; } / / = = = = = = = = = = = = = = = = = = = = = = = the else situation does not discuss the = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = the else {/ / It 's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,  new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { TransactionInfo  txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceedWithInvocation(); } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { // A normal return value: will lead to a commit. return new ThrowableHolder(ex); } } finally { cleanupTransactionInfo(txInfo); }}}); // Check result: It might indicate a Throwable to rethrow. if (result instanceof ThrowableHolder) { throw ((ThrowableHolder) result).getThrowable(); } else { return result; } } catch (ThrowableHolderException ex) { throw ex.getCause(); }}}Copy the code

5, Summary, where else is SqlSession useful?

Mybatis level 1 cache is SqlSession level, as long as SqlSession does not change, then the default cache effect, that is to say, the following code, in fact, will only check the library once:

XxxxxMapper xxxxxMapper = session.getMapper(xxxxxMapper.class); Select id from test_info; xxxxxMapper.selectFromDb(); xxxxxMapper.selectFromDb(); xxxxxMapper.selectFromDb();Copy the code

It’s actually only executed once, so if you’re interested, you can try it out.

Mybatis level 1 cache will not be used when executing the selectFromDb operation.