重点参考(翻译):https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth
1. JDBC是怎么管理事务的
参考:
在学习Spring事务管理之前需要先看下JDBC的事务,因为Spring的事务其实是基于JDBC的。
1.1 JDBC事务中怎么开启事务,提交事务以及事务回流?
用JDBC原生方法写的(伪)代码,可能长这样:
import java.sql.Connection;
Connection connection = dataSource.getConnection(); // (1)
try (connection) {
connection.setAutoCommit(false); // (2)
// execute some SQL statements...
connection.commit(); // (3)
} catch (SQLException e) {
connection.rollback(); // (4)
}
(1)首先需要建立连接,返回Connection
。(另外一种方式:通过DriverManager.getConnection(url, user, password)
,具体#1一开始引用的文章)。
(2)将连接设成不自动提交,即autoCommit=false
(这也是Java代码中开启手动数据库事务的唯一方式)。默认情况下,autoCommit
为true,即每单独一条SQL语句都会自动提交,自成一个事务。
(3)手动提交事务。
(4)发生异常,事务回滚。
上述的伪代码即模拟了JDBC整个事务过程。
1.2 JDBC savepoints的使用
Spring的Propagation值为PROPAGATION_NESTED
时其背后使用的是JDBC的savepoints。Spring的@Transactional的配置可能为:
@Transactional(propagation=TransactionDefinition.NESTED,
isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
基于上述的配置,用JDBC的savepoints实现(伪代码):
import java.sql.Connection;
// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1)
// propagation=TransactionDefinition.NESTED
Savepoint savePoint = connection.setSavepoint(); // (2)
...
connection.rollback(savePoint);
(1)首先设置了Transaction的隔离级别为ISOLATION_READ_UNCOMMITTED
(2)定义新的保存点,并返回一个Savepoint
对象。如果发生异常,则可以使用回滚方法来撤消所有更改或仅保存在保存点之前所做的更改。Savepoint
的支持需要依赖于具体的JDBC驱动/数据库。
具体可以阅读官方文档:https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html
2. Spring Boot中如何管理事务?
Spring Boot中的事务管理和Spring MVC是一样的。
如果要讨论Spring中的事务是如何管理的,那么就可以变成:Spring怎么开启JDBC事务?提交JDBC事务以及回滚JDBC事务?
除此之前,Spring还提供了很多方便的设置来管理事务。
Spring事务管理分类:编程式事务和声明式事务。
2.1 Sping的编程式事务
@Service
public class UserService {
@Autowired
private TransactionTemplate template;
public Long registerUser(User user) {
Long id = template.execute(status -> {
// execute some SQL that e.g.
// inserts the user into the db and returns the autogenerated id
return id;
});
}
}
和JDBC相比:
- 不需要开始或是关闭
connection
(可能还有try-finally的语法),相对应的,使用的是Transaction callbacks,参考:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionCallback.html - 也不需要手动的catch SQLExceptions,因为Spring会把这个异常当作runtime exceptions来处理。
- 当然想要使用
TransactionTemplate
,需要先设置TransactionManager
,并需要传入dataSource
。这些都可以配置成Spring的bean,放到context中。
相对于上述的编程式事务,Spring的声明式事务用的更多。
2.2 Spring声明式事务管理
2.2.1 使用@Transactional注解来实现声明式事务管理
在Spring后来的版本中,或是Spring Boot中,更为常见的是使用@Transactional
注解来声明事务。
UserService
的代码:
public class UserService {
@Transactional
public Long registerUser(User user) {
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
userDao.save(user);
return id;
}
}
总结起来,需要:
- 在Spring Configuration中加
EnableTransactionManagement
注解(Spring Boot会自动装配)。 - 在Spring Configuration中配置transaction manager 。
比如:
@Configuration
@EnableTransactionManagement
public class MySpringConfig {
@Bean
public PlatformTransactionManager txManager() {
return yourTxManager; // more on that later
}
}
对于上述的UserService
,被标注了@Transactional
注解后,其背后Spring做了些什么事?
public class UserService {
public Long registerUser(User user) {
Connection connection = dataSource.getConnection(); // (1)
try (connection) {
connection.setAutoCommit(false); // (1)
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
userDao.save(user); <(2)
connection.commit(); // (1)
} catch (SQLException e) {
connection.rollback(); // (1)
}
}
}
(1)Spring会自动的帮我们建立database connection连接,并将autoCommit
设为false。在方法的最后执行commit()
操作,如果遇到错误,会rollback事务。即JDBC的那套事务管理,Spring会帮我们自动完成。
(2)UserService
中真正的代码,会放在autoCommit=false
之后,但会在commit()之前完成运行。
关于@Transactional
是如何生效的,具体可以查看之前的文章:【每天学点Spring】TransactionInterceptor set up in Spring Boot
关于动态代理,可以查看:【每天学点Spring】cglib以及JDK Dynamic Proxy介绍
总之,上述的(1)Spring是如何帮我们完成的,这里涉及到在createBean的过程中,Spring检测到UserService有@Transactional
,所以会给目标类UserService通过动态代理生成增强类(cglib或是JDK Dynamic Proxy,具体看配置)。
整个过程则会变为:
![](https://img.haomeiwen.com/i15522532/8c4a3156b999f53c.png)
Proxy会帮忙做事务开始,事务提交等事情,
UserService
即我们自己的代码,Proxy会负责帮忙调用。另外,
UserRestController
并不会知道Proxy的存在,因为Spring通过IOC控制反转,在setUserService的时候,会将已经封装过的bean set给Controller。
2.2.2 Transaction Manager(如PlatformTransactionManager)的作用
上述的UserService
会被代理,然后Proxy负责管理事务(而我们要做的只需要在代码中加上@Transactional
注解)。但实际上,并不是Proxy本人帮我们做事务的管理(如开启事务、提交或是关闭事务),Proxy会让Transaction Manager来做这个事情。
Spring提供了PlatformTransactionManager
/TransactionManager
接口,除了接口外,也提供了一些实现类,比如DataSourceTransactionManager
.
当创建一个DataSourceTransactionManager
的时候,需要传入DataSource(因为开启事务的本质是JDBC中的connection
的autoCommit = false
,所以需要用到连接)。
@Bean
public DataSource dataSource() {
return new MysqlDataSource(); // (1)
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource()); // (2)
}
DataSourceTransactionManager
中的开启事务(doBegin)代码片断以及提交事务(doCommit)代码片断:
public class DataSourceTransactionManager implements PlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
Connection newCon = obtainDataSource().getConnection();
// ...
con.setAutoCommit(false);
// yes, that's it!
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
// ...
Connection connection = status.getTransaction().getConnectionHolder().getConnection();
try {
con.commit();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
}
所以DataSourceTransactionManager
的开始事务以及提交事务,跟JDBC中的一样,最终本质调用的就是autoCommit(false)
以及connection.commit()
:
![](https://img.haomeiwen.com/i15522532/661ee18e6805dbe4.png)
【总结】
- 如果Spring发现类的某个方法上有
@Transactional
或是类本身有@Transactional
,那么Spring会给这个bean创建动态代理。 - Proxy将通过Transaction Manager来管理事务(如开启、提交或是回滚事务,方法为:doBegin, doCommit等)。
- Transaction Manager本身会调用JDBC的connection来管理事务,如
autoCommit(false)
以及connection.commit()
。
2.2.3 物理事务(Physical Transactions) vs 逻辑事务(Logical Transactions)
@Service
public class UserService {
@Autowired
private InvoiceService invoiceService;
@Transactional
public void invoice() {
invoiceService.createPdf();
// send invoice as email, etc.
}
}
@Service
public class InvoiceService {
@Transactional
public void createPdf() {
// ...
}
}
UserService
的invoice()方法调用了InvoiceService
的createPdf()方法,这两个方法都有@Transactional注解。
从数据库的事务角度来看,应该要是同一个数据库的事务,即同一个connection.setAutocommit(false),以及commit。Spring把这个叫做physical transaction
(物理事务)。
从Spring的角度来看,上述的代码存在两个逻辑的事务:一个是UserService
,另一个是InvoiceService
。Spring会把这两个同时标注@Transactional
的方法放在同一个physical数据库事务中。
如果我们把createPdf()方法的@Transactional
的propagation改下,会发生什么呢?
@Service
public class InvoiceService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
如果把propagation模式改为requires_new
,意味着Spring在执行createPDF()的时候会重新创建新的独立的事务。即:需要开始两个物理上的事务(两个connection,分别执行setAutocommit(false),commit())。这时候从Spring的角度来看也是有两个逻辑事务,并会将这两个事务分别map到不同的物理事务上。
【总结】
物理事务(Physical Transactions
):即真实的JDBC的事务。
逻辑事务(Logical Transactions
):即被标注@Transactional
注解的方法,即为一个Spring的逻辑事务。
2.2.4 @Transactional Propagation
以下为Propagation的值的列表,以及JDBC会执行的方法:
No. | Propagation | 详解 | JDBC执行方法 |
---|---|---|---|
1 | REQUIRED |
默认的Propagation,我的方法需要一个事务,要么给我创建一个,要么用现在的。 | connection.setAutocommit(false); commit(); |
2 | SUPPORTS |
我不关心是否有事务,如果有也可以,没有也行。 | -- |
3 | MANDATORY |
我不想自己开启一个事务,但如果当前没有事务那也不行(会抛错)。 | -- |
4 | REQUIRES_NEW |
我需要一个全新的事务。 | connection.setAutocommit(false); commit(); |
5 | NOT_SUPPORTED |
我不需要一个事务,如果当前有事务,需要暂停它。 | -- |
6 | NEVER |
我真的不需要一个事务,如果当前有则抛错。 | -- |
7 | NESTED |
嵌套事务呈现父子事务概念,二者之间是有关联的,核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。 | connection.setSavepoint() |
可以看到,多数propagation模式都没有真正做JDBC相关的逻辑,而是提供了更加灵活的对事务的配置。
2.2.5 @Transactional的Isolation模式
以下是一个示例:
@Transactional(isolation = Isolation.REPEATABLE_READ)
关于事务的隔离级别,可以查看Isolation的官网文档:https://www.postgresql.org/docs/9.5/transaction-iso.html
2.2.6 @Transactional失效?
@Service
public class UserService {
@Transactional
public void invoice() {
createPdf();
// send invoice as email, etc.
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
以上是在同一个UserService中有两个方法,并且方法1会调用方法2。虽然这时候方法2的@Transactional
的传播级别设为REQUIRES_NEW
,即需要一个全新的事务。
但事实上,上述的执行过程中,方法2并不会新开启一个事务,原因是Spring的事务是通过给UserService包装一层Proxy,但如果在同一个UserService
里面,没办法再封装proxy,即:方法1调用方法2的时候,并没有通过proxy,而是会直接调,即:
![](https://img.haomeiwen.com/i15522532/6a89e1ef87f9098f.png)
这个是Proxy的边界导致的结果,如果想要改进,可以在方法1调用的时候,先拿到当前对象的proxy,再通过proxy调用方法2(怎么拿到proxy?proxy本质就是Spring Bean=userService)。
3. 当Spring的项目中还有Spring JPA/Hibernate,Transaction Management如何工作?
3.1 如何与JPA / Hibernate同步Spring中的@Transactional
如果我们用Hibernate来操作事务:
public class UserService {
@Autowired
private SessionFactory sessionFactory; // (1)
public void registerUser(User user) {
Session session = sessionFactory.openSession(); // (2)
// lets open up a transaction. remember setAutocommit(false)!
session.beginTransaction();
// save == insert our objects
session.save(user);
// and commit it
session.getTransaction().commit();
// close the session == our jdbc connection
session.close();
}
}
上述的代码为Hibernate使用SessionFactory
来进行数据库等操作。Hibernate会通过SessionFactory
来手动的管理sessions(这里的sessions相当于是database中的connections)以及事务。
那么问题来了:Hibernate怎么知道Spring的@Transactional注解呢?并且Spring的@Transactional也不知道Hibernate的事务。
从结果来看,Spring和Hibernate在事务上确实可以感知到相互的Transaction并且能无缝对接。
示例:
@Service
public class UserService {
@Autowired
private SessionFactory sessionFactory; // (1)
@Transactional
public void registerUser(User user) {
sessionFactory.getCurrentSession().save(user); // (2)
}
}
同样还是Hibernate的SessionFactory
,但是通过@Transactional
的注解,代替了手动的事务管理。
那么其背后的原理是?
3.2 使用HibernateTransactionManager
如果想要@Transactional
与Hibernate的SessionFactory
进行合作的话,那么需要使用HibernateTransactionManager
来替换之前的DataSourcePlatformTransactionManager
。如果使用的是Spring JPA,那么需要使用JpaTransactionManager
来管理事务。
关于HibernateTransactionManager
,参考Spring官网:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/hibernate5/HibernateTransactionManager.html
关于JpaTransactionManager
,参考Spring官网:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/jpa/JpaTransactionManager.html
HibernateTransactionManager
会保证:
- 通过Hibernate来管理事务(如通过SessionFactory)。
- 兼容Spring的@Transactional中的其它代码。
![](https://img.haomeiwen.com/i15522532/34e9761e15695b94.png)
4. 总结
Spring事务,其本质就是通过JDBC管理事务的,所以底层调用的是getConnection().setAutocommit(false);
或connection.commit()
。
本文深度参考了文章开头的链接,如有需要,还请阅读原文。
网友评论