每次在使用Spring事务的时候,都是直接在方法上加上 @Transactional(rollbackFor = Exception.class),之后就没再关系过了,没有细细研究事务的传播行为,导致在使用的时候不得心应手。这次结合"张三"、"李四"的案例来一探究竟。
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时事务是如何传播的。
比如:
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
代码中methodA()方法嵌套调用了methodB() 方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中。默认的是这个。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
Propagation_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
数据库是mysql。
CREATE TABLE `user1` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
CREATE TABLE `user2` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
User1
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User1 {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
}
User2
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User2 {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
}
User1Mapper
@Mapper
public interface User1Mapper extends BaseMapper {
}
User2Mapper
@Mapper
public interface User2Mapper extends BaseMapper {
}
为User1ServiceImpl 和 User2ServiceImpl 相应方法上加入propagation = Propagation.REQUIRED。
User1ServiceImpl
public class User1ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
}
User2ServiceImpl
@Service
public class User2ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User2 user){
user2Mapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
验证一:
@SpringBootTest
public class SpringTest {
@Autowired
private User1ServiceImpl user1Service;
@Autowired
private User2ServiceImpl user2Service;
@Test
void test3() {
User1 user1 = new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2 = new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
}
运行结果
从以上结果来看,插入了张三 和 李四,说明外围方法( void test3() )没有开启事务,里面的两个事务是独立运行的,互不打扰。
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
void test4() {
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
}
运行结果
张三被成功插入了,李四没有。
外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。
下面的例子,我就不一一截图了。以表格的形式,来填写运行结果并进行分析。
这个是使用率比较高的场景。
验证一:
@SpringBootTest
public class SpringTest {
@Autowired
private User1ServiceImpl user1Service;
@Autowired
private User2ServiceImpl user2Service;
//外围方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
@Test
void test3() {
User1 user1 = new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2 = new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Transactional(propagation = Propagation.REQUIRED)
@Test
void test4() {
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
}
验证三:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void transaction_required_required_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addRequiredException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
}
结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
验证一 | “张三”、“李四”均未插入 | 外围方法开启了事务,内部方法加入外围方法事务,外围方法回滚,内部事务也要回滚。 |
验证二 | “张三”、“李四”均未插入 | 外围方法开启了事务,内部方法加入外围方法事务,内部方法抛出异常回滚。外围方法感知异常使整个事务回滚。 |
验证三 | “张三”、“李四”均未插入 | 外围方法开启了事务,内部方法加入了外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然会滚。 |
为User1ServiceImpl 和 User2ServiceImpl相应方法加上Propagation.REQUIRES_NEW属性。
User1ServiceImpl
@Service
public class User1ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(User1 user){
user1Mapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
}
User2ServiceImpl
@Service
public class User2ServiceImpl {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(User2 user){
user2Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void notransaction_exception_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void notransaction_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNewException(user2);
}
}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
验证一 | “张三”插入,“李四”插入 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。 |
验证二 | “张三”插入,“李四”未插入 | 外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常会滚,其他事务不受影响。 |
验证一:
@SpringBootTest
public class SpringTest {
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
user2Service.addRequiresNew(user3);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
user2Service.addRequiresNewException(user3);
}
}
验证三:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
try {
user2Service.addRequiresNewException(user3);
} catch (Exception e) {
System.out.println("回滚");
}
}
}
验证结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
验证一 | “张三”未插入,“李四”插入,“王五”插入 | 外围方法开启事务,插入”张三“方法和外围方法一个事务,插入”李四“方法、插入”王五“方法分别在独立的新建事务中,外围方法抛出异常只会滚和外围方法同一个事务的方法,所以,”张三“的插入方法回滚,”张三“未插入。 |
验证二 | “张三”未插入,“李四”插入,“王五”未插入 | 外围方法开启了事务,插入"张三"方法和外围方法一个事务,插入"李四"方法和插入"王五"方法分别在独立的新建事务中,插入"王五"方法抛出异常,首先插入"王五"方法的事务被回滚,异常继续抛出被外围方法感知到,外围方法事务也会被回滚,所以插入"张三"方法也被回滚。 |
验证三 | "张三"插入,"李四"插入,"王五"未插入。 | 外围方法开启了事务,插入"张三"方法和外围事务是一个事务,插入"李四" 和 插入"王五"方法分别在独立的新建事务中,插入"王五"方法抛出异常,所以插入"王五"方法的事务被回滚,异常被catch不会被外围方法感知到。 |
为User1ServiceImpl 和 User2ServiceImpl相应方法加上Propagation.NESTED属性。
User1ServiceImpl
@Service
public class User1ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.NESTED)
public void addNested(User1 user){
user1Mapper.insert(user);
}
}
User2ServiceImpl
@Service
public class User2ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.NESTED)
public void addNested(User2 user){
user2Mapper.insert(user);
}
@Transactional(propagation = Propagation.NESTED)
public void addNestedException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNested(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
}
验证结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
验证一 | "张三"插入,"李四"插入。 | 外围方法没有开启事务,插入"张三"、“李四"方法分别在自己的事务中独立运行,外围方法抛出异常不影响内部插入"张三”、"李四"方法的独立事务。 |
验证二 | "张三"插入,"李四"未插入。 | 外围方法没有开启事务,插入"张三"、"李四"方法都在自己的事务中独立运行,所以插入"李四"方法抛出异常只会回滚插入"李四"方法,插入"张三"方法不受影响。 |
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Transactional
@Test
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNested(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Transactional
@Test
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNestedException(user2);
}
}
验证三:
@SpringBootTest
public class SpringTest {
//省略其他...
@Transactional
@Test
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addNestedException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
}
验证结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
验证一 | “张三”、"李四"都未插入。 | 外围方法开启了事务,内部事务为外围方法的子事务,外围方法回滚,内部方法也要回滚。 |
验证二 | “张三”、"李四"都未插入。 | 外围方法开启了事务,内部事务为外围事务的子事务,内部方法抛出异常,切外围方法能感知到异常,所以整个事务回滚。 |
验证三 | "张三"插入、"李四"未插入。 | 外围方法开启了事务,内部事务为外围方法的子事务,插入"李四"的内部方法抛出了异常,catch 住,可以单独对子事务回滚。 |
为User1ServiceImpl 和 User2ServiceImpl相应方法加上Propagation.SUPPORTS属性。
User1ServiceImpl
@Service
public class User1ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.SUPPORTS)
public void addSupports(User1 user){
user1Mapper.insert(user);
}
}
User2ServiceImpl
@Service
public class User2ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.SUPPORTS)
public void addSupportsException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void addSupports(User2 user){
user2Mapper.insert(user);
}
}
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_supports_supports_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addSupports(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addSupportsException(user2);
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_exception_supports_supports(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addSupports(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addSupports(user2);
throw new RuntimeException();
}
}
验证结果:
验证方法序号 | 数据库结果 | 数据库结果分析 |
---|---|---|
验证一 | “张三”、“李四” 都被插入 | 外围方法没有开启事务,插入"张三"、“李四"都是以非事务方式来运行的,所以"张三”、“李四” 都被插入。 |
验证二 | “张三”、“李四” 都被插入 | 外围方法没有开启事务,插入"张三"、“李四"都是以非事务方式来运行的,所以"张三”、“李四” 都被插入。 |
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_supports_supports_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addSupports(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addSupportsException(user2);
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_supports_supports(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addSupports(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addSupports(user2);
throw new RuntimeException();
}
}
验证三:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_supports_supports_try_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addSupports(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addSupportsException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
}
验证结果:
验证方法序号 | 数据库结果 | 数据库结果分析 |
---|---|---|
验证一 | “张三”、“李四” 都未插入 | 外围方法开启了事务,插入"张三"、"李四"的方法支持当前事务,插入"李四"方法抛出了异常,被外围方法感知到,所以整个外围方法事务回滚。 |
验证二 | “张三”、“李四” 都未插入 | 外围方法开启了事务,插入"张三"、"李四"的方法支持当前事务,外围方法抛出了异常,整个外围方法事务回滚。 |
验证三 | “张三”、“李四” 都未插入 | 外围方法开启了事务,插入"张三"、"李四"的方法支持当前事务,插入"李四"方法抛出了异常,被catch,虽然不被外围方法感知到,但同属于一个事务,所以整个外围方法事务回滚。 |
这个结论跟Propagation.REQUIRED结论一致。
为User1ServiceImpl 和 User2ServiceImpl相应方法加上Propagation.NOT_SUPPORTED属性。
User1ServiceImpl
@Service
public class User1ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addNotSupported(User1 user){
user1Mapper.insert(user);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addNotSupportedException(User1 user){
user1Mapper.insert(user);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly=true)
public User1 getNotSupported(Integer id){
return user1Mapper.selectById(id);
}
}
User2ServiceImpl
@Service
public class User2ServiceImpl {
//省略其他...
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addNotSupported(User2 user){
user2Mapper.insert(user);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addNotSupportedException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_exception_required_notSuppored(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNotSupported(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
public void notransaction_required_notSuppored_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNotSupportedException(user2);
}
}
验证结果:
验证方法序号 | 数据库结果 | 数据库结果分析 |
---|---|---|
验证一 | “张三”、“李四” 都插入 | 外围方法没有开启事务,插入"张三" 方法被Propagation.REQUIRED修饰,独立新建一个事务,所以,"张三"被插入,插入"李四"方法以非事务方式运行,也被插入。 |
验证二 | “张三”、“李四” 都插入 | 外围方法没有开启事务,插入"张三" 方法被Propagation.REQUIRED修饰,独立新建一个事务,所以"张三"被插入。插入"李四"方法即使抛出异常,但是是以非事务方式运行的,所以"李四"被插入。 |
验证一:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void transaction_exception_required_notSuppored(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNotSupported(user2);
throw new RuntimeException();
}
}
验证二:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void transaction_required_notSuppored_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addNotSupportedException(user2);
}
}
验证三:
@SpringBootTest
public class SpringTest {
//省略其他...
@Test
@Transactional
public void transaction_required_notSuppored_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addNotSupportedException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
}
验证结果:
验证方法序号 | 数据库结果 | 数据库结果分析 |
---|---|---|
验证一 | "张三"未插入、“李四” 插入 | 外围方法开启了事务,插入"张三" 方法跟外围方法同属于一个事务,外围方法抛出异常,所以"张三"未被插入,插入"李四"方法被Propagation.NOT_SUPPORTED修饰,外围有事务,就将外围事务挂起,即使外围方法抛出了异常,插入"李四" 方法是以非事务方法运行,也感知不到,所以"李四"被插入。 |
验证二 | "张三"未插入、“李四” 插入 | 插入"李四"方法被Propagation.NOT_SUPPORTED修饰,所以addNotSupportedException()方法就不支持事务了,所以方法即使运行异常也不会造成此方法的数据回滚,故"李四"被插入到数据库。 |
外围方法开启了事务,插入"张三"方法跟外围方法同属于一个事务,因为addNotSupportedException()方法抛出了异常致使外围方法的事务进行回滚,那自然插入"张三"方法也会回滚,所以"张三"没有被插入到数据库。 | ||
验证三 | "张三"未插入、“李四” 插入 | 插入"李四"方法被Propagation.NOT_SUPPORTED修饰,所以addNotSupportedException()方法就不支持事务了,所以方法即使运行异常也不会造成此方法的数据回滚,故"李四"被插入到数据库。 |
外围方法开启了事务,插入"张三"方法跟外围方法同属于一个事务,因为addNotSupportedException()方法抛出了异常致使外围方法的事务进行回滚,那自然插入"张三"方法也会回滚,所以"张三"没有被插入到数据库。 |
Propagation.NEVER 和 Propagation.MANDATORY都很简单,就不一一进行案例分析了。