spring transaction and jdbc transaction

Fundamentals of Spring Transactions

The essence of Spring transaction is actually the support of the database for transactions. Without the transaction support of the database, spring cannot provide transaction functions. For pure JDBC operation database, if you want to use transaction, you can follow the following steps:

  1. Get the connection Connection con = DriverManager.getConnection()
  2. Open transaction con.setAutoCommit(true/false);
  3. Execute CRUD
  4. Commit transaction/rollback transaction con.commit() / con.rollback();
  5. Close the connection conn.close();

After using Spring's transaction management function, we can no longer write the code of steps 2 and 4, but Spirng will do it automatically. So how does Spring open and close transactions before and after the CRUD we write? To solve this problem, we can understand Spring's transaction management implementation principle as a whole. The following is a brief introduction, the annotation method is an example

  1. The configuration file enables annotation-driven, and is identified by the annotation @Transactional on the relevant classes and methods.
  2. When spring starts, it will parse and generate related beans. At this time, it will check the classes and methods with related annotations, generate proxies for these classes and methods, and perform related configuration injection according to the relevant parameters of @Transaction, so that in the proxy In the middle, we have dealt with the related transactions (open the normal commit transaction, and abnormally roll back the transaction).
  3. The transaction commit and rollback of the real database layer is implemented through binlog or redo log.

 

Spring's transaction mechanism

All data access technologies have transaction processing mechanisms, which provide APIs to start transactions, commit transactions to complete data operations, or roll back data in the event of an error.

Spring's transaction mechanism uses a unified mechanism to handle transaction processing of different data access technologies. Spring's transaction mechanism provides a PlatformTransactionManager interface, and transactions of different data access technologies are implemented using different interfaces, as shown in the table.

Data Access Technology and Implementation

data access technology accomplish
JDBC DataSourceTransactionManager
JPA JapTransactionManager
Hibernate HibernateTransactionManager
JDO JdoTransactionManager
Distributed transaction JtaTransactionManager

The code to define the transaction manager in the program is as follows:

@Bean 
public PlatformTransactionManager transactionManager() { 

    JpaTransactionManager transactionManager = new JpaTransactionManager(); 
    transactionManager.setDataSource(dataSource()); 
    return transactionManager; 
}

notional affairs

Spring supports declarative transactions, that is, using annotations to select methods that need to use transactions, it uses the @Transactional annotation to indicate on the method that the method requires transaction support. This is an AOP-based implementation operation.

@Transactional 
public void saveSomething(Long  id, String name) { 
    //数据库操作 
}

It is important to note here that this @Transactional annotation comes from the org.springframework.transaction.annotation package, not javax.transaction.

Two implementations of AOP proxies:

  • JDK is a proxy interface, and private methods will not exist in the interface, so they will not be intercepted;
  • cglib is a subclass, and the private method will not appear in the subclass, nor can it be intercepted.

Java dynamic proxy.

There are four steps as follows:

  1. Create your own invocation handler by implementing the InvocationHandler interface;
  2. Create a dynamic proxy class by specifying a ClassLoader object and a set of interfaces for the Proxy class;
  3. The constructor of the dynamic proxy class is obtained through the reflection mechanism, and its only parameter type is the interface type of the calling processor;
  4. The dynamic proxy class instance is created through the constructor, and the handler object is passed in as a parameter during construction.

GCLIB proxy

cglib (Code Generation Library) is a powerful, high-performance, high-quality code generation library. It can extend Java classes and implement Java interfaces at runtime.

  • cglib encapsulates asm, which can dynamically generate new classes ( subclasses ) at runtime.
  • cglib is used for AOP, the proxy in jdk must be based on the interface, but cglib does not have this restriction.

Principle difference:

Java dynamic proxy uses the reflection mechanism to generate an anonymous class that implements the proxy interface, and calls InvokeHandler to process it before calling the specific method. The cglib dynamic proxy uses the asm open source package, loads the class file of the proxy object class, and generates subclasses by modifying its bytecode.

  1. If the target object implements the interface, the JDK's dynamic proxy will be used to implement AOP by default.
  2. If the target object implements the interface, you can force the use of CGLIB to implement AOP
  3. If the target object does not implement the interface, the CGLIB library must be used, and spring will automatically convert between the JDK dynamic proxy and CGLIB

If it is an internal method of a class instead of a proxy, you can maintain a proxy for its own instance at this time.

@Service
public class PersonServiceImpl implements PersonService {
    @Autowired
    PersonRepository personRepository;

    // 注入自身代理对象,在本类内部方法调用事务的传递性才会生效
    @Autowired
    PersonService selfProxyPersonService;

    /**
     * 测试事务的传递性
     *
     * @param person
     * @return
     */
    @Transactional
    public Person save(Person person) {
        Person p = personRepository.save(person);
        try {
            // 新开事务 独立回滚
            selfProxyPersonService.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 使用当前事务 全部回滚
            selfProxyPersonService.save2(person);
        } catch (Exception e) {
            e.printStackTrace();
        }
        personRepository.save(person);

        return p;
    }

    @Transactional
    public void save2(Person person) {
        personRepository.save(person);
        throw new RuntimeException();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void delete() {
        personRepository.delete(1L);
        throw new RuntimeException();
    }
}

Propagation properties of Spring transactions

The so-called propagation property of spring transaction is to define how spring should handle the behavior of these transactions when multiple transactions exist at the same time. These properties are defined in TransactionDefinition, and the explanation of specific constants is shown in the following table:

constant name Constant explanation
PROPAGATION_REQUIRED Support the current transaction, if there is no current transaction, create a new transaction. This is the most common choice and is Spring's default transaction propagation.
PROPAGATION_REQUIRES_NEW Create a new transaction. If there is a current transaction, suspend the current transaction. The newly created transaction will have nothing to do with the suspended transaction. It is two independent transactions. After the outer transaction fails and rolls back, the execution result of the inner transaction cannot be rolled back. The inner transaction fails and an exception is thrown, and the outer transaction fails. Capture, or do not handle rollback operations
PROPAGATION_SUPPORTS Support the current transaction, if there is no current transaction, it will be executed in a non-transactional mode.
PROPAGATION_MANDATORY Supports the current transaction and throws an exception if there is no current transaction.
PROPAGATION_NOT_SUPPORTED Execute the operation in a non-transactional manner, suspending the current transaction if there is a current transaction.
PROPAGATION_NEVER Executes non-transactionally and throws an exception if a transaction currently exists.
PROPAGATION_NESTED If an active transaction exists, run in a nested transaction. If there is no active transaction, it is executed according to the REQUIRED attribute. It uses a single transaction with multiple savepoints that can be rolled back. The rollback of the inner transaction does not affect the outer transaction. It only works on the DataSourceTransactionManager transaction manager.

Database isolation level

isolation level The value of the isolation level cause problems
Read-Uncommitted 0 cause dirty reads
Read-Committed 1 Avoid dirty reads, allow non-repeatable reads and phantom reads
Repeatable-Read 2 Avoid dirty reads, non-repeatable reads, allow phantom reads
Serializable 3 Serialized read, transactions can only be executed one by one, avoiding dirty reads, non-repeatable reads, and phantom reads. The execution efficiency is slow, use it with caution

Dirty read: A transaction adds, deletes, or modifies data, but it is not committed, and another transaction can read the uncommitted data. If the first transaction rolls back at this time, then the second transaction has read the dirty data.

Non-repeatable read: Two read operations occur in a transaction. Between the first read operation and the second operation, another transaction modifies the data. At this time, the two read data are inconsistent.

Phantom read: The first transaction modifies a certain range of data in batches, and the second transaction adds a piece of data to this range. At this time, the first transaction will lose the modification of the newly added data.

Summarize:

The higher the isolation level, the better the data integrity and consistency can be guaranteed, but the greater the impact on concurrent performance.

The default isolation level of most databases is Read Commited, such as SqlServer, Oracle

The default isolation level of a few databases is: Repeatable Read For example: MySQL InnoDB

Isolation Levels in Spring

constant explain
ISOLATION_DEFAULT This is the default isolation level of PlatfromTransactionManager, which uses the default transaction isolation level of the database. The other four correspond to the isolation levels of JDBC.
ISOLATION_READ_UNCOMMITTED This is the minimum isolation level for a transaction, which allows another transaction to see uncommitted data from this transaction. This isolation level produces dirty reads, non-repeatable reads and phantom reads.
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

事务的嵌套

通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(spring 默认)

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。

他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

void methodA() { 

        try { 

            ServiceB.methodB(); 

        } catch (SomeException) { 

            // 执行其他业务, 如 ServiceC.methodC(); 

        } 

    }

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback

另外三种事务传播属性基本用不到,在此不做分析。

总结

对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。

Spring Boot 对事务的支持

通过org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration类。我们可以看出Spring Boot自动开启了对注解事务的支持
Spring

只读事务(@Transactional(readOnly = true))的一些概念

  • 概念:

从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。

@Transcational(readOnly=true) 这个注解一般会写在业务类上,或者其方法上,用来对其添加事务控制。当括号中添加readOnly=true, 则会告诉底层数据源,这个是一个只读事务,对于JDBC而言,只读事务会有一定的速度优化。而这样写的话,事务控制的其他配置则采用默认值,事务的隔离级别(isolation) 为DEFAULT,也就是跟随底层数据源的隔离级别,事务的传播行为(propagation)则是REQUIRED,所以还是会有事务存在,一代在代码中抛出RuntimeException,依然会导致事务回滚。

  • 应用场合:
  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324642189&siteId=291194637