Spring中事务的隔离级别

最近学习了公司的分布式事务框架,这个是基于Spring的事务来实现的,其中涉及到Spring事务的隔离级别,联想到平时项目的实现中经常会用到分布式事务,偶尔还会用到嵌套式事务,我对此一知半解,所以借此机会,正好系统地学习一下Spring的事务隔离级别。

详细展开前,先汇总一下Spring的几种事务隔离级别:

  • PROPAGATION_REQUIRED【Spring默认的事务隔离级别】
  • PROPAGATION_SUPPORTS
  • PROPAGATION_MANDATORY
  • PROPAGATION_REQUIRES_NEW]
  • PROPAGATION_NOT_SUPPORTED
  • PROPAGATION_NEVER
  • PROPAGATION_NESTED

PROPAGATION_REQUIRED

默认的事务传播特性,通常情况下我们用这个事务传播特性就可以了。

如果当前事务上下文中没有事务,那么就会新创建一个事务。如果当前的事务上下文中已经有一个事务了,那么新开启的事务会开启一个新的逻辑事务范围,但是会和原有的事务共用一个物理事务,我们暂且不关心逻辑事务和物理事务的区别,先看看这样会导致怎样的代码行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void txRollbackInnerTxRollbackPropagationRequires() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
// 内部事务设置了 setRollbackOnly,
status.setRollbackOnly();
}
});
}
});
}
`

这段代码在一个嵌套的事务内部(内外层的事务都是PROPAGATION_REQUIRED的)设置了回滚,那么对于外部的事务来说,它会收到一个UnexpectedRollbackException,因为内部的事务和外部的事务是共用一个物理事务的,所以显然内部的事务必然会导致外部事务的回滚,但是因为这个回滚并不是外部事务自己设置的,所以外层事务回滚的时候会需要抛出一个UnexpectedRollbackException,让事务的调用方知道这个回滚并不是外部事务自己想要回滚,是始料未及的。

但是,如果内层的事务不是通过设置setRollbackOnly()来回滚,而是抛出了RuntimeException来回滚,那么外层的事务接收到了内层抛出的RuntimeException也会跟着回滚,这个是可以预料到的行为,所以不会有UnexpectedRollbackException

PROPAGATION_SUPPORTS

PROPAGATION_SUPPORTS的特性是如果事务上下文中已经存在一个事务,那么新的事务(传播特性为 PROPAGATION_SUPPORTS)就会和原来的事务共用一个物理事务,其行为和PROPAGATION_REQUIRED一样。但是,如果当前事务上下文中没有事务,那么PROPAGATION_SUPPORTS就按无事务的方式执行代码。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void txRollbackInnerTxRollbackPropagationSupports() {
supportsTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
throw new CustomRuntimeException();
}
});
}

看上面这段代码,虽然我们在事务(PROPAGATION_SUPPORTS的)中抛出了一个RuntimeException,但是因为其事务上下文中没有事务存在,所以这段代码实际上是以无事务的方式执行的,因此代码中的jdbcTemplate.update()操作也不会被回滚。

PROPAGATION_MANDATORY

PROPAGATION_MANDATORY要求事务上下文中必须存在事务,如果事务上下文中存在事务,那么其行为和PROPAGATION_REQUIRED一样。如果当前事务上下文中没有事务,那么就会抛出IllegalTransactionStateException,比如下面这段代码就会这样。

1
2
3
4
5
6
7
8
9
public void txRollbackInnerTxRollbackPropagationMandatory() {
mandatoryTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
}
});
}

PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW无论当前事务上下文中有没有事务,都会开启一个新的事务,并且和原来的事务完全是隔离的,外层事务的回滚不会影响到内层的事务,内层事务的回滚也不会影响到外层的事务(这个说法得稍微打点折扣:因为如果内层抛出RuntimeException的话,那么外层还是会收到这个异常并且触发回滚),我们分析下几段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void txRollbackInnerTxRollbackPropagationRequiresNew() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
}
});

// 外部事务发生回滚,内部事务应该不受影响还是能够提交
throw new RuntimeException();
}
});
}

这段代码外层的事务回滚了,但是不会影响到内层的事务的提交,内层事务不受外层的事务的影响。

再看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void txRollbackInnerTxRollbackPropagationRequiresNew2() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
// Nested transaction committed.
requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
// 内部事务发生回滚,但是外部事务不应该发生回滚
status.setRollbackOnly();
}
});
}
});
}

这段代码在内层事务上设置了setRollbackOnly,内层事务肯定会回滚,但是由于内层事务和外层事务是隔离的,所以外层事务不会被回滚。

再看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void txRollbackInnerTxRollbackPropagationRequiresNew3() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");

requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
// 内部事务抛出 RuntimeException,外部事务接收到异常,依旧会发生回滚
throw new RuntimeException();
}
});
}
});
}

这段代码在内层事务抛出了一个RuntimeException,虽然内层事务和外层事务在事务上是隔离,但是RuntimeException显然还会抛到外层去,所以外层事务也会发生回滚。

PROPAGATION_NOT_SUPPORTED

PROPAGATION_NOT_SUPPORTED不管当前事务上下文中有没有事务,代码都会在按照无事务的方式执行.

看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void txRollbackInnerTxRollbackPropagationNotSupport() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
notSupportedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
}
});
// 外部事务回滚,不会把内部的也连着回滚
transactionStatus.setRollbackOnly();
}
});
}

上面这段代码中虽然外部事务发生了回滚,但是由于内部的事务是PROPAGATION_NOT_SUPPORTED,根本不在外层的事务范围内,所以内层事务不会发生回滚。

PROPAGATION_NEVER

PROPAGATION_NEVER如果当前事务上下文中存在事务,就会抛出IllegalTransactionStateException异常,自己也会按照非事务的方式执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void txRollbackInnerTxRollbackPropagationNever2() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");
neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
}
});
}
});
}

比如上面这段代码,在一个PROPAGATION_REQUIRES里面嵌入了一个PROPAGATION_NEVER,内层就会抛出一个IllegalTransactionStateException,导致外层事务被回滚。

PROPAGATION_NESTED

PROPAGATION_NESTED只能应用于像DataSource这样的事务,可以通过在一个事务内部开启一个PROPAGATION_NESTED而达到一个事务内部有多个保存点的效果。一个内嵌的事务发生回滚,只会回滚到它自己的保存点,外层事务还会继续,比如下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void txRollbackInnerTxRollbackPropagationNested() {
nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
"1111112");

nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into user (name, password) values (?, ?)",
"Huang", "1111112");
// 内部事务设置了 rollbackOnly,外部事务应该不受影响,可以继续提交
status.setRollbackOnly();
}
});
}
});
}

内层的事务发生了回滚,只会回滚其内部的操作,不会影响到外层的事务。