配置类
package transaction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement
public class Config {
@Bean
public DriverManagerDataSource driverManagerDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://10.100.0.176:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public TestTx testTx() {
return new TestTx();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
事务方法
package transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
public class TestTx {
@Autowired
private JdbcTemplate jdbcTemplate;
private int count;
@Autowired
private TestTx testTxAgent;
@Transactional(propagation = Propagation.REQUIRED)
public void b(int id, String name) {
throw new RuntimeException("updateNameWithException1");
}
public void be(int id, String name) {
jdbcTemplate.update("update t_test set name=? where id=?", name, id);
}
@Transactional(propagation = Propagation.REQUIRED)
public TestBean a(int id) {
TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), id);
Assert.notNull(testBean, "testBean not be null");
try {
testTxAgent.b(id, testBean.getName() + "-" + count++);
} catch (Exception e) {
e.printStackTrace();
}
be(id,"be-name");
return testBean;
}
}
class TestBean {
private Integer id;
private String name;
private Double age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getAge() {
return age;
}
public void setAge(Double age) {
this.age = age;
}
@Override
public String toString() {
return "TestBean{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
DDL
-- auto-generated definition
create table t_student
(
id int auto_increment,
age int null,
name varchar(50) null,
constraint t_student_id_uindex
unique (id)
);
alter table t_student
add primary key (id);
主启动类
package transaction;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.sql.SQLException;
public class MainTest
{
public static void main(String[] args) throws SQLException {
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
TestTx testTx = context.getBean(TestTx.class);
TestBean testBeanById = testTx.a(1);
System.out.println(testBeanById);
}catch (Throwable e){
e.printStackTrace();;
}
}
}
下面的这些例子都是围绕上面的代码来测试。没有额外说明的话,都是a->b,上面方法中的@Transactional
会变。不是上面例子中一成不变的。
通过上一篇文章,已经知道,最终操作的是Connection。所谓挂起,就是将之前的事务的状态变为当前事务的一个属性。事务的传播级别上面已经说的很清楚了,这里想总体的区分一下。
主要分为:
下面的组合也是按照这种的思路来分析。下面只说异常的情况,正常的情况是没有可说的。
还有一点,可以指定异常回滚,要是当前的异常和指定的异常不匹配,还是会找RuntimeException和Error,这里我抛出的异常都是RuntimeException的子类。所以,这里就不需要指定异常了。
现在有两个方法。A和B。
这个要求能组合的传播级别不多。a可以为(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b只能为(PROPAGATION_REQUIRES_NEW)。
b和a里面的事务都会回滚。
分析:
之前对事务的实现(TransactionInterceptor做了分析),异常会被catch掉,做事务的回滚操作,但是这个异常还会继续的抛出去。同样的逻辑,这个异常会被调用a的时候的
TransactionInterceptor
掉,同样的也是会做事务的回滚的。当前提条件是异常需要回滚。如果不需要回滚就会提交掉,当然需要注意的是,a方法中调用b方法之后的代码是不会执行的。如果提交也是提前调用b方法之前的操作,回滚也是。
b回滚,a提交
分析:
a和b是两个独立的事务,在
TransactionInterceptor
的catch块里面捕获了异常,在处理事务的回滚之后,会将原始的异常抛出来,在调用b之后,处理完b事务的回滚之后,会将原始的异常抛出来。但是这个时候被a在内部catch了,这个异常并没有抛到TransactionInterceptor里面的catch块。所以对于a来说,一切都是正常的。所以,他的事务会提交。
这意思就是a得先有一个事务,b才能复用a中的事务。a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_REQUIRED)
注意:这里从头到尾就一个事务,b用的是a的事务。
a和b的操作都没有生效,并且事务回滚。
分析:
和上面是一样的模式,TransactionInterceptor捕获了异常,做回滚操作,之后将原始的异常抛出,同样的逻辑,在a里面做了回滚操作。
需要注意的一点:
a和b用的是同一个事务,也就是同一个Connection对象,在调用b的异常之后,TransactionInterceptor里面做回滚操作,不会真正的去做回滚(也就是说不会调用Connection.rollback()方法),只是设置了一下ConnectionHolder的一个标志位(Rollback only为true),表示这个事务出现了异常,需要做回滚。真正处理回滚的地方应该是那个方法创建的Connection,它就应该做真正的回滚操作。
但是因为a调用b的时候,并没有用catch块捕获,原始的异常还是可以被TransactionInterceptor捕获到,去做回滚操作,a是真正创建connection的,所以,真正的回滚操作也得是它来。
a和b的操作都没有生效,并且事务回滚,抛出了一个怪异的错误Transaction rolled back because it has been marked as rollback-only
分析:
接着上面的说,b异常被a内部catch了,a对应的TransactionInterceptor无法获取到异常信息,catch块走不了,只能走提交操作,在提交的时候发现当前ConnectionHolder的Rollback属性为true,就会去做回滚操作,会真正的调用connection的rollback()方法,并且抛出
UnexpectedRollbackException
。长事务:就是多个方法引用的是同一个事务,并且这些方法没有savePoint的操作,这就是长事务,一般默认,长事务中只有要一个报错,整个事务都会失败。
直接看代码更加的清晰
做回滚(AbstractPlatformTransactionManager#processRollback(DefaultTransactionStatus,boolean))
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
// 是否有savePoint,判断的顺序是关键,如果b为nested的话,a中处理了异常是不会有问题的。因为没有设置rollback only属性
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
// 回滚到savePoint,并且释放到savePoint
status.rollbackToHeldSavepoint();
}
// 是否是新的事务,也就是说这个事务是否是由这个方法创建的。如
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);// 这个回滚操作很简单,就是connection.rollback方法
}
else {
// 上述条件不满足,就是一个长事务。
if (status.hasTransaction()) {
// 可以直接改TransactionStatus的对应的值,也可以直接改AbstractPlatformTransactionManager中的方法返回值。
// 基本上来说AbstractPlatformTransactionManager为true
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 设置ConnectionHolder的rollback only
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// 这个为true直接抛出异常,
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
调用该方法的地方多着呢,unexpectedRollback的为true主要是在事务做提交的时候传递的。
做提交(AbstractPlatformTransactionManager#commit(TransactionStatus))
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 先判断DefaultTransactionStatus的rollback only
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
// 这里传递的是true,这会判断connectionHolder中rollback only的值。
// shouldCommitOnGlobalRollbackOnly也可以重写
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
// 这里才会做提交操作。
processCommit(defStatus);
}
对照代码来看上面的例子就比较清晰了。
这意思就是a得先有一个事务,b才能复用a中的事务。和上一个一样,但是b只能是PROPAGATION_NESTED。a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_NESTED)
a和b的操作都没有生效,并且事务回滚。
分析
这个原因和上面的是一样,没有try catch,会导致异常直接到a相关的TransactionInterceptor里面,捕获异常做回滚。
b方法回滚,a方法执行成功。事务提交,a和b用是同一个事务。
分析:
如果存在事务,nested会按照嵌套的方式来执行,其实就是利用SavePoint,a开始一个事务,执行到b的时候。b对应的TransactionInterceptor在调用b方法之前会在当前的事务的Connection里面保存一个savePoint,在执行完成之后会释放savePoint。
如果b出现了异常,在上面的例子中已经分析了出现异常之后的回滚操作,一上来就会检查是否有savePoint,如果有会回滚到savePoint。不会设置rollback only。对这个这个例子来说,异常已经捕获到了,a对应的TransactionInterceptor没有捕获到异常,并且也不需要rollback only,所以事务正常提交。
这里a和b用的是同一个事务。这就是嵌套事务,外层事务回滚会导致内层事务也回滚,内存事务回滚不会导致外层事务回滚。这话其实不对,就没有内外层事务之说,从始到终一个事务。
这种组合方式比较多,可以是a方法有事务,b方法没有事务,这样的组合有a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_NOT_SUPPORTED)。也可以是a b 方法都没有事务,这样的组合有a(PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER),b(PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER)。
b中按照没有事务的方式运行,a中事务回滚
分析:
b按照没有事务的方式运行,所以每一行的sql操作都是事务的。所以,b中的修改成功了。
但是这个异常还是抛给了a,a所对应的TransactionInterceptor会捕获异常,做事务的回滚操作。所以,a中的修改没有作用。
b中按照没有事务的方式运行,a中事务提交
分析:
异常在a中已经处理掉了,所以a对应的TransactionInterceptor没有异常,正常提交
a和b都按照非事务的方式来执行了,所以没什么好说的了
既然都不需要事务了,为啥不直接去掉这两注解呢?
这两注解只是Connection相关的操作没有了,但是在Spring事务里面除了Connecton之外,还有别的,比如和事务相关的资源(TransactionSynchronization),他会走完事务操作的流程,支持在一些真正需要回滚的地方没有操作而已。
这其实就是PROPAGATION_NEVER
了,有事务就直接报错。
这其实就是PROPAGATION_MANDATORY
了,没有事务就直接报错,但是他不会创建一个新的事务,只会复用之前的事务。那这个逻辑就和a和b都是PROPAGATION_SUPPORTS差不多了。长事务的概念。