preface

An in-depth understanding of Spring source code is divided into seven sections. This section is the fifth section of Spring source code.

  1. The scanning process
  2. Bean creation process
  3. Container extension
  4. AOP source code analysis

5. Transaction source code analysis

  1. Spring JDBC source code analysis
  2. Spring common design patterns

This chapter builds on section 4 by looking at how adding the @Transactional annotation enables automatic rollback and automatic commit when using JdbcTemplate, since the core implementation still uses AOP.

JdbcTemplate is a class provided by Spring that executes SQL statements. It relies on a DataSource. A DataSource is a DataSource, and it’s an interface. The implementation class DriverManagerDataSource, which contains the information needed to connect to the database, is used for simple CRUD. However, when using transactions, you need to add TransactionManager. Neither is necessary for the @Transactional case.

Usually we use it this way.

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
   @Bean
   public DataSource dataSource(a) {
      DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource("jdbc:mysql://localhost:3306/test");
      driverManagerDataSource.setUsername("root");
      driverManagerDataSource.setPassword("hxl..");
      driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
      return driverManagerDataSource;
   }
   @Bean
   public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
      DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
      return dataSourceTransactionManager;
   }
   @Bean
   public JdbcTemplate jdbcTemplate(a) {
      return newJdbcTemplate(dataSource()); }}Copy the code

There’s one more important thing you need to do. This is the key to implementing a COMMIT and a rollback, because if you want to rollback, at least in the same Connection, for example, the following code wants to rollback. Spring must have caught the exception at the top and then called the Rollback () method on Connection, but the question is how does the upper code know about the Connection at the bottom?

@Transactional()
public void test(a) {
   jdbcTemplate.update("UPDATE test set name =111 where id =1");
    throw new RuntimeException();
}
Copy the code

Spring creates a Connection itself and puts it into a ThreadLocal before entering the target method. The jdbcTemplate object in the underlying code takes the Connection from ThreadLocal and creates it itself if it doesn’t. The Connection is known to the underlying code. But the reverse is also possible, because a Connection created in both places is bound to ThreadLocal, and the correct process is upper level creation.

Is used to manage the ThreadLocal TransactionSynchronizationManager, can be obtained by the following code, resourceMap holds the current Connection, Its key is the DataSource we added to the container above.

@Transactional(noRollbackForClassName = {"org.springframework.test.TestException"})
public void print(a) {
   jdbcTemplate.update("UPDATE test set name =111 where id =1");
   Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
   throw new RuntimeException();
}
Copy the code

@EnableTransactionManagement

We begin analyze source, is first and foremost @ EnableTransactionManagement annotations, he will give in to container Import a TransactionManagementConfigurationSelector class, he inherited ImportSelector, So he might also import multiple beans into the container, as described in Chapter 2.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}
Copy the code

Total import two here, ProxyTransactionManagementConfiguration, InfrastructureAdvisorAutoProxyCreator.

But ProxyTransactionManagementConfiguration also defines three key beans will be added to the container, as follows.

BeanFactoryTransactionAttributeSourceAdvisor

TransactionAttributeSource

TransactionInterceptor
Copy the code

The first class is the notifier, which contains the following two objects. The TransactionInterceptor is the method interceptor. All methods marked Transactional enter this class first.

TransactionAttributeSource used to get in the way that @ Transactional information

InfrastructureAdvisorAutoProxyCreator

InfrastructureAdvisorAutoProxyCreator implements the BeanPostProcessor, mainly used for the marked @ the method’s class generation of Transactional proxy objects.

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if(bean ! =null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {return wrapIfNecessary(bean, beanName, cacheKey);// Create a proxy object if the bean is qualified to be proxied}}return bean;
}
Copy the code

This proxy qualification is used to determine whether a class has the @Transactional annotation on it or a class method that has the @Transactional annotation. As in the previous chapter, the implementation of all advisors is obtained. Above by @ Import into a implementation class Advisor BeanFactoryTransactionAttributeSourceAdvisor, here alone demo under his role.

This code is used to judge did on the test method of UserTest @ Transactional annotation, or on the class, so it returns true, whether for generated proxy for this object, here the logic implementation is also can be put in the @ Transactional methods, classes, the key on the interface.

BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(new AnnotationTransactionAttributeSource());
MethodMatcher methodMatcher = advisor.getPointcut().getMethodMatcher();
boolean test = methodMatcher.matches(UserTest.class.getDeclaredMethod("test"), UserTest.class);

System.out.println(test);
Copy the code

I won’t bother with the logic, it’s pretty simple.

TransactionInterceptor

When looking at the TransactionInterceptor, the Transactional method marked @Transactional comes in here, implemented under its invokeWithinTransaction() method.

First determines whether the transaction manager is ReactiveTransactionManager subclass, usually are not, the transaction manager is called reactive, not really.

The transaction manager is our hand movements, adding the container was able to achieve, generally use DataSourceTransactionManager.

if (this.reactiveAdapterRegistry ! =null && tm instanceof ReactiveTransactionManager) {
}
Copy the code

It will then go in here and create the transaction information.

TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Copy the code

We won’t cover the rest, but the key points are to create a Connection and not commit automatically, and bind the Connection to ThreadLocal in preparation for the JdbcTemplate, which is done by doBegin below.

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;
   try {
      /** * If Connection does not exist, create a new Connection */
      if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }
      txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
      con = txObject.getConnectionHolder().getConnection();
      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);
      txObject.setReadOnly(definition.isReadOnly());
      if (con.getAutoCommit()) {
         txObject.setMustRestoreAutoCommit(true);
         /** * set not to commit automatically */
         con.setAutoCommit(false);
      }
      prepareTransactionalConnection(con, definition);
      txObject.getConnectionHolder().setTransactionActive(true);
      int timeout = determineTimeout(definition);
      if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }/** * bind the Connection */ in ThreadLocal
      if(txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}catch (Throwable ex) {
   }
}
Copy the code

Call the original method

The next sentence calls the method we wrote.

Object retVal;
try {
    // Call our method
   retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// Throw an exception and try to roll back
   completeTransactionAfterThrowing(txInfo, ex);
   throw ex;
}
finally {
   cleanupTransactionInfo(txInfo);
}
Copy the code

Suppose we execute this SQL.

jdbcTemplate.update("UPDATE test set name =111 where id =1");
Copy the code

So don’t even think about, first of all must be get Connection, will try to get from TransactionSynchronizationManager first, because it has already been created Connection, so here direct return, or create a new Connection.

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
   ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
   if(conHolder ! =null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
      conHolder.requested();
      if(! conHolder.hasConnection()) { conHolder.setConnection(fetchConnection(dataSource)); }return conHolder.getConnection();
   }
   /** * new connection */
   Connection con = fetchConnection(dataSource);
   return con;
}
Copy the code

Try to roll back

If our method throws an exception, it will go into the following method and roll back, but not all exceptions will be rolled back, depending on the information you configure.

completeTransactionAfterThrowing(txInfo, ex);
Copy the code

Back on the Transactional annotation, there are two values.

@Target({ElementType.TYPE, ElementType @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { Class
      [] rollbackFor() default {}; Class
      [] noRollbackFor() default {}; }Copy the code

RollbackFor is used to rollback exceptions of this type

NoRollbackFor is used to roll back exceptions that are not of this type.

By default, none is configured, and the rollback condition is either a subclass of RuntimeException or an Error subclass.

return (ex instanceof RuntimeException || ex instanceof Error);
Copy the code

submit

Finally, commit to complete the process

protected void commitTrajavansactionAfterReturning(@Nullable TransactionInfo txInfo) {
   if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
      if (logger.isTraceEnabled()) {
         logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); }}Copy the code

Transaction is a complex process, in addition to rollback, submit, there will be other things to manage, such as transmission behavior, behavior is when a transaction method by another transaction method calls, is running in the current transaction, or to open a new transaction, and in their own affairs, Spring defines seven communication behavior, The default is Propagation.REQUIRED, which means to run in the current transaction.