前言
Spring为我们提供了巨大的便利,其中的事务增强特性,使得程序出错时,不需要我们进行手动回滚。Spring在Propagation定义了7种事务的传播行为:
public enum Propagation { REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), NEVER(TransactionDefinition.PROPAGATION_NEVER), NESTED(TransactionDefinition.PROPAGATION_NESTED); }
一上来,我不想全部抛出各个行为的定义以及说明,我们用代码来验证
代码环境搭建
使用SpringBoot+Mybatis-plus进行搭建,目录结构大致如下:
(1)在数据库中创建User表
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增序号', `name` varchar(255) DEFAULT NULL COMMENT '姓名', PRIMARY KEY (`id`) ) ENGINE = InnoDB ;
对应的实体类:
package com.example.transaction.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; public class User { @TableId(value = "id", type = IdType.AUTO) private Integer id; private String name; public User() { } public User(Integer id, String name) { this.id = id; this.name = name; } //get、set }
(2)pom.xml引入以下依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> </dependencies>
(3)application.yaml
server: port: 8080 spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver username: root password: a123 url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
(4)启动类加上:
@MapperScan(basePackages = {"com.example.transaction.dao"})
(5)UserMapper
package com.example.transaction.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.transaction.entity.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<User> { }
因为使用的Mybatis-plus,我们其实不需要在UserMapper新增curd方法,BaseMapper里面基本上都有了
(6)UserService
package com.example.transaction.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.transaction.entity.User; public interface UserService extends IService<User> { public void insertChild1(); public void insertChild2(); }
(7)UserServiceImpl
package com.example.transaction.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.transaction.dao.UserMapper; import com.example.transaction.entity.User; import com.example.transaction.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Autowired private UserMapper userMapper; @Override public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); } }
UserServiceImpl我们增加3个insert方法,用户姓名与方法名对应
REQUIRED
(1)insertChild1与insertChild2方法开启事务。外围方法不开启事务,且抛出异常
@Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); }
测试方法不开启事务
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); int a = 1 / 0; }
运行后,外围方法(测试方法)抛出异常,但数据库中有child1与child2数据。可以发现,insertChild1与insertChild2方法在自己的事务中运行,外围方法出现异常不影响这两个彼此独立的事务。
(2)insertChild1与insertChild2方法开启事务,但insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中只有child1数据。insertChild1与insertChild2分别在自己的事务中运行,insertChild2抛出异常不影响insertChild1事务,但insertChild2方***回滚。
(3)insertChild1与insertChild2方法开启事务,且他们都不抛出异常。外围方法也开启事务,并抛出异常
@Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); }
测试方法:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); int a = 1 / 0; }
运行后,外围方法抛出异常,数据库中没有child1与child2数据,说明insertChild1与insertChild2被外围事务影响,都进行了回滚操作。可以看得出来,insertChild1与insertChild2放弃了自己的事务,都加入了外围事务。
(4)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常。外围方法也开启事务,不抛出异常
@Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中没有一条数据。insertChild1与insertChild2放弃了自己的事务,加入到了外围事务中,insertChild2方法抛出异常,被外围事务感知,致使整体进行回滚。
(5)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常并捕获异常。外围方法开启事务
@Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); try { int a = 1 / 0; } catch (Exception e) { } }
测试方法:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后发现,没有出现报错,但数据库依然没有一条数据。可以发现,两个子方法加入了外围事务,insertChild2捕获了自己的异常不被外围方法感知,但整个事务依然被回滚。
当然,如果insertChild2没有捕获自己的异常,但在外围方法捕获insertChild2的异常,整个事务依然被回滚。
到这里,我们可以总结REQUIRED的特性:
外围方法没开启事务的情况下,内部方法使用PROPAGATION_REQUIRED会开启自己独立的一个事务,多个内部方法开启的事务互不影响。
外围事务开启事务的情况下,内部方法使用PROPAGATION_REQUIRED,则内部方***放弃自己的事务,一起加入到外围事务中,任意的一个内部方法抛出异常,就算自己捕获异常,同样都会使得整个事务回滚。
SUPPORTS
(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.SUPPORTS) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.SUPPORTS) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,但数据库中有child1与child2数据。可以发现,当内部方法开启SUPPORTS时,外界方法不开启事务时,则内部方法都不使用事务。
(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务
@Override @Transactional(propagation = Propagation.SUPPORTS) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.SUPPORTS) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库没有任何数据。可以发现,当内部方法开启SUPPORTS时,外界方法也开启事务时,则内部方法一起加入到外围事务中。
到这里,我们可以总结SUPPORTS的特性:
外围方法没有开启事务,内部方法尽管开启SUPPORTS,最后都不会使用事务。
外围事务开启事务时,内部方法开启SUPPORTS,则一起加入到外围事务中,是一个整体事务。
MANDATORY
(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.MANDATORY) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.MANDATORY) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,直接抛出了一个新异常:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
内部方法使用mandatory时,外围方法必须使用事务,否则直接报错。
(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务
与上一个情景比,仅仅是测试方法加了一个事务
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中没有任何数据。显然,此时insertChild1与insertChild2全部加入到了外围事务中,变成了一个整体事务。
到这里,我们可以总结MANDATORY的特性:
外围方法没有开启事务,子方法使用MANDATORY时,运行时直接报错,子方法需要外围方法开启事务。
外围方法开始事务时,子方法使用MANDATORY时,所有子方法一起加入到外围事务中,形成一个整体事务。
REQUIRES_NEW
(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中只有child1数据。说明当insertChild1与insertChild2使用REQUIRES_NEW时,外围方法没开启事务,则子方***创建独立的事务,互不干扰。insertChild2抛出异常便只回滚了child2数据。
(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务
与上一个情景相比,仅仅是测试方法开启了事务:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中还是只有child1数据。说明当insertChild1与insertChild2使用REQUIRES_NEW时,外围方法开启事务时,子方法还是会创建独立的事务,互不干扰。insertChild2抛出异常便只回滚了child2数据。
到这里,我们可以总结REQUIRES_NEW的特性:
不管外围方法有没有开始事务。子方法使用REQUIRES_NEW时,都会开启自己独立的事务,这些子方法的独立事务互不干扰,与外围方法也互不干扰。
NOT_SUPPORTED
(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中有child1与child2两条数据。insertChild1与insertChild2使用NOT_SUPPORTED时,外围方法不开启事务,则子方法也不开启事务。
(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务
与上一个情景相比,仅仅是测试方法开启了事务:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中依然有child1与child2两条数据。insertChild1与insertChild2使用NOT_SUPPORTED时,外围方法开启事务,子方法还是不开启事务。
到这里,我们可以总结NOT_SUPPORTED的特性:
外围事务不开启事务,子方法使用NOT_SUPPORTED时,子方法都不使用事务。
外围方法开启事务时,子方法使用NOT_SUPPORTED时,子方法将外围事务挂起,也就是不使用事务。
NEVER
(1)insertChild1使用NEVER,外围方法开启事务
@Override @Transactional(propagation = Propagation.NEVER) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); }
测试方法:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); }
运行后,直接抛出了一个新异常:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
内部方法使用NEVER时,如果外围方法开启事务后,则直接报错,内部方法强制不使用事务。
到这里,我们可以直接总结NEVER的特性:
子方法使用NEVER,则子方法需要以非事务方式执行,如果外围方法开启事务,则直接报错
NESTED
(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务
@Override @Transactional(propagation = Propagation.NESTED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.NESTED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); int a = 1 / 0; }
测试方法:
@Test public void testUser() { userService.insertChild1(); userService.insertChild2(); }
运行后,insertChild2抛出异常,数据库中仅仅有child1数据,说明insertChild2方法被回滚了。可以看出,insertChild1与insertChild2方法使用NESTED时,而外围方法不开始事务,则insertChild1与insertChild2方***创建相互独立的事务,insertChild2抛出异常,因此回滚了child2数据。
(2)insertChild1与insertChild2方法开启事务,外围方法开启事务
@Override @Transactional(propagation = Propagation.NESTED) public void insertChild1() { User user = new User(); user.setName("child1"); userMapper.insert(user); } @Override @Transactional(propagation = Propagation.NESTED) public void insertChild2() { User user = new User(); user.setName("child2"); userMapper.insert(user); }
测试方法:
@Test @Transactional(propagation = Propagation.REQUIRED) public void testUser() { userService.insertChild1(); userService.insertChild2(); }
虽然没有出现任何异常,但所有数据都被回滚了,控制台打印出如下信息
Rolled back transaction for test: [DefaultTestContext@4229bb3f testClass = TransactionApplicationTests, testInstance = com.example.transaction.TransactionApplicationTests@7c711375, testMethod = testUser@TransactionApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@56cdfb3b testClass = TransactionApplicationTests, locations = '{}', classes = '{class com.example.transaction.TransactionApplication, class com.example.transaction.TransactionApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@17046283, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@5a63f509, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@7a69b07, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@c33b74f], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]
猜测可能是哪个地方不支持NESTED,或者说我哪里写错了?这里无法试验出NESTED完整的特性。
如果,支持NESTED的话,可以总结NESTED的特性:
如果外围方法没开启事务,此时同REQUIRED
如果外围方法开启事务,外围事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务
总结
我们使用一张表,来总结七种行为的特性(传播行为是针对于子方法的):
传播行为 | 特性 |
REQUIRED |
|
SUPPORTS |
|
MANDATORY |
|
REQUIRES_NEW |
|
NOT_SUPPORTED |
|
NEVER |
|
NESTED |
|