preface

We all know that Spring provides us with a lot of abstractions, such as transactional abstractions when we are working with databases, making it very easy to manipulate databases transactional. Whether you use JDBC, Mybatis, Hibernate, etc., or whether you use DataSource or JTA, Spring Transaction Abstraction management is a good way to integrate it. Let’s look at the abstract core interface of things

Spring transaction Abstraction

PlatformTransactionManager is things manager interface

// The transaction manager interface has the following interfaces: get transaction information, commit, and rollback
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}
Copy the code

Common transaction managers are the following:

  • DataSourceTransactionManager
  • HibernateTransactionManager
  • JtaTransactionManager

These managers realize the PlatformTransactionManager’s three interfaces, implementation logic is slightly different, but the difference is not big for the user

Define things some of the parameters, the parameters of some things in TransactionDefinition. In Java, details are as follows:

public interface TransactionDefinition {
	  int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
		// The default isolation level, which is the same as the database isolation level
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
		// No timeout by default
    int TIMEOUT_DEFAULT = -1;
}
Copy the code

The following two graphs illustrate these parameters:

Seven transaction propagation features:

Four transaction isolation levels:

What are dirty reads, unrepeatable reads, and phantom reads before looking at transaction isolation levels

Dirty read:Dirty reads are uncommitted data from one object that is read by another object, which is obviously unacceptable

Unrepeatable read:Non-repeatable read means that the same data is read for many times in a transaction and the read results are inconsistent.

Phantom reads:Transaction A makes A change to one data in the table that involves all rows in the table. Transaction B also modifies the data in the table by inserting a new row into the table. Then the user of transaction A will find that there are still unmodified rows in the table, as if in an illusion

With these concepts in mind, let’s look at the isolation level:

Here we can see, the Spring is not provide all things management implementation, but provides a standard PlatformTransactionManager manager operation interface, and regulating the conduct of its implementation by each platform to realize the specific things. This is Spring’s transaction abstraction.

Spring programming things

Spring provides the TransactionTemplate utility class to make it easy to use programmatic transactions. By default TransactionTemplate use DataSourceTransactionManager. In the Spring context, we can get the TransactionTemplate without configuring the TransactionTemplate bean. Take the following example.

@Service
public class UserInfoService {

    @Resource
    private UserInfoDAO userInfoDAO;
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void updateUser1(a){
        transactionTemplate.execute(transactionStatus -> {
            userInfoDAO.updateUserName(1."zhangsanfeng");
            transactionTemplate.execute(transactionStatus2 -> {
                userInfoDAO.updateUserName(2."lisi");
                return null;
            });
            return null; }); }}Copy the code

Since Spring’s default thing propagation feature is PROPAGATION_REQUIRED, let’s verify that this is true

As you can see from the above two figures, the newTransaction property in TransactionStatus, the first true and the second false, matches what is described as PROPAGATION_REQUIRED. Other propagation features can be verified on their own.

Declarative thing

In addition to programmatic things, Spring also provides us with declarative things. Use the @Transactional annotation. Transactional can work on interfaces, interface methods, classes, and class methods. When used on a class, all public methods of that class will have transaction attributes of that type, and we can also use this annotation at the method level to override class-level definitions.

Although the @Transactional annotation can be applied to interfaces, interface methods, classes, and class methods, Spring recommends not using the annotation on interfaces or interface methods because it only works when using an interface-based proxy. In addition, the @Transactional annotation should only be applied to public methods, due to the nature of Spring AOP. If you use the @Transactional annotation ona protected, private, or default visibility method, this is ignored and no exceptions are raised.

The rollbackFor attribute of @Transactional sets up an array of Throwable to indicate that the transaction rolls back if the method throws these exceptions. By default, if the rollbackFor attribute is not configured, the transaction will be rolled back only when a RuntimeException is encountered. The following code thing will not take effect:

    @Transactional
    public void updateUser2(a) throws Exception {
        int r1 = userInfoDAO.updateUserName(1."wanger");
        int r2 = userInfoDAO.updateUserName(2."mawu");
        if (r2==1) {throw newException(); }}Copy the code

If we change the exception thrown to RuntimeException, then things will take effect. Or specify exceptions to make things work, such as @transactional (rollbackFor = exception.class).

Why does something work with the @Transactional annotation?

This is because the Spring container generates a proxy object for the annotated object, which, when actually called, is the proxy object being called. The implementation of proxy objects is an AOP enhancement that implements the implementation of things.

How are the specified propagation characteristics and isolation levels implemented through annotations?

public @interface Transactional {
    @AliasFor("transactionManager")
    String value(a) default "";

    @AliasFor("value")
    String transactionManager(a) default "";

    String[] label() default {};

    Propagation propagation(a) default Propagation.REQUIRED;

    Isolation isolation(a) default Isolation.DEFAULT;

    int timeout(a) default- 1;

    String timeoutString(a) default "";

    boolean readOnly(a) default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
Copy the code

As you can see from the code, we can specify isolation levels and propagation properties, which Spring will read in the enhanced logic when it generates the proxy class for us.

8 cases of things failing and solutions

The database engine does not support transactions

MySQL MyISAM engine does not support transactions. InnoDB is the engine that supports transactions. InnoDB is used to support transactions.

Not managed by Spring

There is no point in talking about Spring transaction management without Spring management.

Methods are not public

Transactional can only be used with public methods, otherwise transactions will not fail. To use a non-public method, enable the AspectJ proxy mode.

The data source is not configured with a transaction manager

That is, transaction management is disabled. If Springboot is not enabled, perform the following operations.

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
Copy the code

If is SpringBoot, can be annotated @ EnableTransactionManagement directly on to start the class.

The propagation characteristics are mismatched

Propagation.NOT_SUPPORTED or Propagation.NOT_SUPPORTED.

Exception type error

Because the default exception type is runtime exception, it does not take effect if other exceptions are thrown. Transactional(@transactional (rollbackFor = exception.class))

The exception is eaten

Things don’t work if you code like this:

    @Transactional(rollbackFor = Exception.class)
    public void updateUser2(a) {
        try {
        int r1 = userInfoDAO.updateUserName(1."3");
        int r2 = userInfoDAO.updateUserName(2."4");
        if (r2==1) {throw newRuntimeException(); }}catch (Exception e){
            
        }
    }
Copy the code

Solution: You must throw an exception, or Spring transaction management will not go to the rollback logic

Class invocation

@Service
public class UserInfoService {
    public void justUpdate(a){
        updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2(a) {}}Copy the code

The above code does not take effect because the internal invocation does not involve invocation of the proxy class, and there is no AOP enhancement, so it will not take effect. Solution: 1. Self-injection

@Service
public class UserInfoService {
   @Autowired
    private UserInfoService userInfoService;
    public void justUpdate(a){
        userInfoService.updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2(a) {}}Copy the code

2. Spring context

@Service
public class UserInfoService {
    ApplicationContext applicationContext;
    public void justUpdate(a){
		   UserInfoService userInfoService = (UserInfoService) applicationContext.getBean("userInfoService");
        userInfoService.updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2(a) {}}Copy the code

3. Get his proxy class and call it directly

@Service
public class UserInfoService {
    public void justUpdate(a){
		   ((UserInfoService) AopContext.currentProxy()).updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2(a) {}}Copy the code

— — — — — — — — — — — — — — — — — — — — — — — — — — — — END — — — — — — — — — — — — — — — — — — — — — — — — — — — more Spring related knowledge, pay attention to me, each platform is the same ID