学习东西要知行合一,若是只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来咱们一块儿实践来学习Spring事务的传播属性。程序员
传播属性
传播属性定义的是当一个事务方法碰到另外一个事务方法时的处理行为,一共有七种行为,定义以下spring
传播性 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED |
0 | 支持当前事务,若是没有就新建事务 |
PROPAGATION_SUPPORTS |
1 | 支持当前事务,若是没有就不以事务的方式运行 |
PROPAGATION_MANDATORY |
2 | 支持当前事务,若是当前没事务就抛异常 |
PROPAGATION_REQUIRES_NEW |
3 | 不管当前是否有事务,都会新起一个事务 |
PROPAGATION_NOT_SUPPORTED |
4 | 不支持事务,若是当前存在事务,就将此事务挂起不以事务方式运行 |
PROPAGATION_NEVER |
5 | 不支持事务,若是有事务就抛异常 |
PROPAGATION_NESTED |
6 | 若是当前存在事务,在当前事务中再新起一个事务 |
其实只看概念的话已经很直截了当了说明了每一个传播性的做用,此时咱们再用具体的例子演示一下每一个传播性属性下的行为。sql
这次演示咱们使用的是H2数据库,这个数据库是做用在内存里面的,因此对于咱们演示事务效果来讲正好,无需咱们在进行其余的配置了,咱们新建一个表。将下面语句放在schema.sql
文件里面便可,SpringBoot程序在启动的时候就会自动为咱们在内存里面创建这样的一个表。数据库
CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));复制代码
演示以前咱们会定义两个类FooService
和BarService
。咱们使用BarService
里面的方法进行调用FooService
中的方法。后端
环境准备
在进行事务演示以前,其实能够分为如下几种状况,根据排列组合,咱们能够得出如下八种状况bash
- 调用者:有无事务
- 调用者:是否有异常
- 被调用者:有无事务**(这个是经过传播属性进行控制的)**因此并不在排列组合中
- 被调用者:是否有异常
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
有 | 有 | 有 |
有 | 有 | 无 |
有 | 无 | 有 |
有 | 无 | 无 |
无 | 有 | 有 |
无 | 有 | 无 |
无 | 无 | 有 |
无 | 无 | 无 |
异常类
其中的RollbackException
是咱们本身定义的一个异常类微信
@Service
public class BarServiceImpl implements BarService{
@Autowired
private FooService fooService;
// PROPAGATION_REQUIRED演示 无事务
@Override
public void testRequiredNoTransactional() throws RollbackException {
fooService.testRequiredTransactional();
}
}
复制代码
调用者
在BarService
中定义两个方法,一个是带着事务的,一个是不带事务的前后端分离
// 有事务
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {
}
// 无事务
@Override
public void noTransactional() throws RollbackException {
}
复制代码
接下来咱们就根据俄上面定义的八种状况进行事务传播属性的学习。ide
PROPAGATION_REQUIRED
在此传播属性下,被调用方是否新建事务取决去调用者是否带着事务。微服务
想要了解这个传播属性的特性,其实咱们演示上面八种状况的两个例子就够了
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
- 第一种状况咱们在被调用者抛出异常的状况下,若是查询不到插入的数据,那么就说明被调用者在调用者没有事务的状况下本身新建了事务。
- 第二种状况咱们在调用者抛出异常的状况下,若是查询不到插入的数据,那么就说明被调用者在调用者有事务的状况下就加入当前事务了。
咱们先来看一下被调用者的类的方法例子。
@Service
public class FooServiceImpl implements FooService {
@Autowired
private JdbcTemplate jdbcTemplate;
// REQUIRED传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void testRequiredHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")");
throw new RollbackException();
}
// REQUIRED传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void testRequiredNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
}
}
复制代码
接下来咱们看一下调用者方法的例子
@Service
public class BarServiceImpl implements BarService{
@Autowired
private FooService fooService;
// 有事务
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {
// 调用者有事务,抛异常 被调用者无异常
fooService.testRequiredNoException();
throw new RollbackException();
}
// 无事务
@Override
public void noTransactional() throws RollbackException {
// 调用者无事务,不抛异常 被调用者有异常
fooService.testRequiredHasException();
}
}
复制代码
此时咱们在程序调用时进行查询
String noException = Global.REQUIRED_NO_EXCEPTION;
String hasException = Global.REQUIRED_HAS_EXCEPTION;
try {
barService.noTransactional();
}catch (Exception e){
log.info("第一种状况 {}",
jdbcTemplate
.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
}
try {
barService.hasTransactional();
}catch (Exception e){
log.info("第二种状况 {}",
jdbcTemplate
.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
}
复制代码
查看打印出来的日志
2019-10-16 13:02:04.142 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 0
2019-10-16 13:02:04.143 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 0
复制代码
咱们看到咱们都没有查到相应的数据,说明数据都回滚了。此时咱们应该就理解了那句话支持当前事务,若是没有就新建事务。
PROPAGATION_SUPPORTS
被调用者是否有事务,彻底依赖于调用者,调用者有事务则有事务,调用者没事务则没事务。
接下来咱们仍是用上面的两个例子进行演示
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
- 第一种状况:被调用者抛出异常的状况下,若是仍能查询到数据,说明事务没有回滚,说明被调用者没有事务
- 第二种状况:调用者抛出异常状况下,若是查不到数据,说明两个方法在一个事务中
接下来仍然是例子演示
被调用者,只是将@Transactional
注解中的propagation
属性更换为了Propagation.SUPPORTS
// SUPPORTS传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
throw new RollbackException();
}
// SUPPORTS传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}
复制代码
调用者和上面的例子调用同样,咱们直接查看执行效果
2019-10-16 13:50:27.738 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 1
2019-10-16 13:50:27.741 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 0
复制代码
咱们看到了在第一种状况下查到了数据,说明在第一种状况下被调用者是没有事务的。此时咱们应该就理解了这句话 支持当前事务,若是没有就不以事务的方式运行。
PROPAGATION_MANDATORY
依然是这两个例子进行演示
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
- 第一种状况:由于调用者没有事务,因此此传播属性下应该是抛异常的
- 第二种状况:被调用者的事务和调用者事务是一样的
接下来是被调用者的代码例子
// MANDATORY传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
throw new RollbackException();
}
// MANDATORY传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}
复制代码
调用者和上面的例子调用同样,咱们直接查看执行效果
2019-10-16 13:58:39.178 ERROR 12317 --- [ main] c.e.t.t.TransactionApplication : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
2019-10-16 13:58:39.276 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 0
2019-10-16 13:58:39.281 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 0
复制代码
咱们发现和咱们推测同样,说明被调用者是不会本身新建事务的,此时咱们应该就理解了这句话支持当前事务,若是当前没事务就抛异常。
PROPAGATION_REQUIRES_NEW
此传播属性下,不管调用者是否有事务,被调用者都会新建一个事务
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
- 第一种状况:调用者无事务,被调用者会新建事务,因此查不到数据
- 第二种状况:调用者有事务,被调用者会新建一个事务,因此调用者抛异常影响不到被调用者,因此能查到数据
接下来咱们演示代码。
被调用者
// REQUIRES_NEW传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')");
throw new RollbackException();
}
// REQUIRES_NEW传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')");
}
复制代码
调用者的例子和上面的相同,咱们直接来看执行状况
2019-10-16 16:29:20.296 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 0
2019-10-16 16:29:20.298 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 1
复制代码
咱们发现和咱们的推论是同样的,说明调用者的事务和被调用者的事务彻底无关。此时咱们应该就理解这句话了不管当前是否有事务,都会新起一个事务。
PROPAGATION_NOT_SUPPORTED
不管调用者是否有事务,被调用者都不以事务的方法运行
一样是这两个例子
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
- 第一种状况:被调用者都不会有事务,那么在抛异常以后就能查到相应的数据
- 第二种状况:在调用者有事务的状况下,被调用者也会在无事务环境下运行,因此咱们依然能查到数据
接下来验证咱们的猜想
// NOT_SUPPORTED传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')");
throw new RollbackException();
}
// NOT_SUPPORTED传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')");
}
复制代码
而后查看执行结果
2019-10-16 16:38:35.065 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 1
2019-10-16 16:38:35.067 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 1
复制代码
咱们能够看到在最后两种状况都查到了数据,根据演示效果应该能够理解这句话了,不支持事务,若是当前存在事务,就将此事务挂起不以事务方式运行。
PROPAGATION_NEVER
调用者有事务,被调用者就会抛出异常
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
这个就不演示,相信你们看到这里应该都会明白在第一种状况下咱们是可以查到数据的。在第二种状况下因为调用者带着事务,因此会抛异常。
PROPAGATION_NESTED
此传播属性下,被调用者的事务是调用者的事务的子集。
咱们重点说一下NESTED
的传播属性的特性
调用者是否有事务 | 说明 |
---|---|
有 | 被调用者会新起一个事务,此事务和调用者事务是一个嵌套的关系 |
无 | 被调用者会本身新起一个事务 |
关于什么是嵌套事务的关系,咱们用下面三个例子可以进行演示。
调用者是否有事务 | 调用者是否有异常 | 被调用者是否有异常 |
---|---|---|
无 | 无 | 有 |
有 | 有 | 无 |
有 | 无 | 有 |
- 第一种状况:若是查不到数据,则说明在调用者无事务状况下,被调用者会新起一个事务
- 第二种状况:若是查不到数据,说明外层事务可以影响内层事务
- 第三种状况:若是查到数据,说明内层事务不影响外层事务
接下来咱们编写具体的代码
// NESTED传播属性-回滚事务
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedHasException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')");
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RollbackException();
}
// NESTED传播属性-不回滚事务
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedNoException() throws RollbackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
}
复制代码
而后接下来的调用者也会有点区别
@Override
@Transactional()
public void hasTransactionalNoException() throws RollbackException {
// NESTED传播属性 - 调用者有事务,不抛异常 被调用者有异常
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
fooService.testNestedHasException();
}
复制代码
而后执行效果
2019-10-16 18:01:06.387 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第一种状况 0
2019-10-16 18:01:06.389 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第二种状况 0
2019-10-16 18:01:06.390 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第三种状况 1
复制代码
能够看出来嵌套事务的本质就是外层会影响内层,内层不影响外层。而REQUIRES_NEW
则是互不影响。
总结
到如今咱们已经所有分析完了七种传播属性,从写这篇文章开始到结束其中也碰到过一些坑,有些是不本身实践一遍是根本不知道的,因此我仍是建议读者看完这篇文章之后本身进行实践,演示各类状况,只有这样才可以烂熟于心。