一、什么是事务
事务(Transaction)是并发控制单位,是用户定义的一个操作序列,这些操作要么都做,要么都不做,是一个不可分割的工作单位。
二、事务的传播
1. 不使用事务
(1)创建StuService 类
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,当执行到1/0时提示报错,此时数据库的结果如下所示。成功保存了parent和child-1的数据。
2. 开启事务 REQUIRED
场景一:父方法开启REQUIRED事务,子方法不开启事务
(1)创建StuService 类
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(此处开启事务REQUIRED)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,同样的,当执行到1/0时提示报错,此时数据库的结果如下所示。此时数据库中没有保存任何数据。这是因为作为父方法的testPropagationTrans上面我们此时加上了 @Transactional(propagation = Propagation.REQUIRED)注解,它是有事务的。虽然在该方法调用的调用的两个子方法上面没有添加事务的注解,但是父方法此时添加的这个REQUIRED事务会传递到调用的子方法上。所以,当saveChildren()方法发生异常以后,整个父方法及其所调用的方法都会发生回滚。
场景二:父方法不开启事务,子方法开启事务
(1)创建StuService 类(此处开启事务REQUIRED)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(此处开启事务REQUIRED)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,同样的,当执行到1/0时提示报错,此时数据库的结果如下所示。正如最开始所说的,如果当前所在的整个方法体里面,它本身就不带有事务的话,它会重新的去创建一个事务的。也就是当前saveChildren()会存在于一个事务中,而saveParent()不存在事务,所以此时成功保存了parent的数据至数据库。
3. 开启事务 SUPPORTS
当父方法和子方法同时开启事务时,子方法即与父方法存在于同一个事务中,即子方法此时带有事务,所以它不会去创建自己的事务。此时结果与场景一相同,此处不做演示。
(1)创建StuService 类(此处开启事务SUPPORTS)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,同样的,当执行到1/0时提示报错,此时数据库的结果如下所示。因为父方法testPropagationTrans()没有使用事务,而子方法saveChildren()使用SUPPORTS事务时,会跟着父方法。如果父方法没有事务,则子方法没有事务;如果父方法有事务,则子方法就有事务。
如果在这个例子的基础上,给testPropagationTrans()方法加上@Transactional(propagation = Propagation.REQUIRED)注解,则数据库将不会保存任何数据。
4. 开启事务 MANDATORY
(1)创建StuService 类(此处开启事务MANDATORY)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法。此时会报IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’。
当使用MANDATORY事务注解时,如果父方法(即调用方)不存在事务的话,会抛出一个异常,该注解会强制要求调用方必须存在事务。在该例子的基础上,给testPropagationTrans()方法加上@Transactional(propagation = Propagation.REQUIRED)注解。运行测试方法时,就不会再报IllegalTransactionStateException异常。运行结果将与事务 REQUIRED场景一相同。
5 开启事务 REQUIRES_NEW
场景一:
(1)创建StuService 类(此处开启事务REQUIRES_NEW)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
//@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,同样的,当执行到1/0时提示报错,此时数据库的结果如下所示。可以看到,数据库这个时候成功保存了一条记录。这条记录就是在父方法(调用方)testPropagationTrans()中在调用saveParent()的时候所保存的。而另一个被调用的saveChildren()方法因为开启了事务REQUIRES_NEW,所以saveChildren()方法会生成改方法的事务,并且与saveParent()方法是分开的,当saveChildren()发生异常时,它自己发生了回滚。
场景二:
(1)创建StuService 类(此处开启事务REQUIRES_NEW)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(开启事务REQUIRED)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren方法,同样的,当执行到1/0时提示报错,此时数据库的结果如下所示。数据库没有保存任何记录。在这个过程中,saveChildren()方法是另外的一个新的事务,它报错后会将异常给到父方法testPropagationTrans(),而父方法中调用了saveParent(),所以父方法的事务会使saveParent()进行回滚。
场景三:
(1)创建StuService 类(此处开启事务REQUIRES_NEW,并将异常语句int a = 1/0;注释)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren(){
saveChildren1();
//int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(开启事务REQUIRED,并增加异常语句int a = 1/0;)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
int a = 1/0;
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren()方法,此时saveChildren()方法是没有异常的。此时执行结果如下,成功保存了两条child数据。虽然父方法此时开启了事务REQUIRED,但是子方法开启了新事物REQUIRES_NEW,所以父方法中的异常不会影响子方法saveChildren()的异常。
6 开启事务 NOT_SUPPORTS
场景一:
(1)创建StuService 类(此处开启事务NOT_SUPPORTS)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.NOT_SUPPORTS)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
//@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren()方法,当执行到1/0时提示报错,此时数据库的结果如下所示。首先,父方法testPropagationTrans()没有事务,而且子方法saveChildren()开启NOT_SUPPORTS后,它本身也就没有事务,所以,此时尽管子方法报异常,它也能成功保存child-1的数据。
场景二:
(1)创建StuService 类(此处开启事务NOT_SUPPORTS)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.NOT_SUPPORTS)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(开启事务REQUIRED)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,会调用StuService 类的saveChildren()方法,当执行到1/0时提示报错,此时数据库的结果如下所示。因为此时子方法不支持事务,所以child-1没有被回滚;而父方法此时是有事务的,所以会回滚parent数据而没有被保存。
7 开启事务 NERVER
(1)创建StuService 类(此处开启事务NERVER)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.NERVER)
public void saveChildren(){
saveChildren1();
int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(开启事务REQUIRED)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,此时会直接报异常IllegalTransactionStateException: Existing transaction found for transaction maeked with propagation ‘never’ 。此时不会执行方法,也就不会有任何数据的保存或者回滚。
如果此时把父方法testPropagationTrans()的事务去掉,重新执行测试方法。数据库中保存的数据如下。
7 开启事务 NESTED
(1)创建StuService 类(此处开启事务NESTED,注释掉异常语句int a = 1/0;)
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveParent(){
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu);
}
@Transactional(propagation = Propagation.NESTED)
public void saveChildren(){
saveChildren1();
//int a = 1/0;
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
}
}
(2)创建TestTransServiceImpl 类(开启事务REQUIRED,增加异常语句int a = 1/0;)
@Service
public class TestTransServiceImpl {
@Autowired
private StuService stuService;
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans(){
stuService.saveParent();
stuService.saveChildren();
int a = 1/0;
}
}
(3)创建测试类TransTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {
@Autowired
private StuService stuService;
@Autowired
private TestTransServiceImpl testTransService;
@Test
public void myTest(){
testTransService.testPropagationTrans();
}
}
直接执行测试类的方法,此时会直接报异常IllegalTransactionStateException: Existing transaction found for transaction maeked with propagation ‘never’ 。此时不会执行方法,也就不会有任何数据的保存或者回滚。
如果此时把父方法testPropagationTrans()的事务去掉,重新执行测试方法。数据库中保存的数据如下。
小结
-
REQUIRED
使用当前的事务,如果当前没有事务,则自己新建一个事务,子方法是必须运行在一个事务中的;如果当前存在事务,则加入这个事务,成为一个整体。在事务的传播里面,默认的就是REQUIRED,而REQUIRED也是比较多的用于增删改这些操作,所以一般使用REQUIRED就可以了。
举例:领导没饭吃,小明有钱,小明会自己买了自己吃;领导有的吃,会分给小明一起吃。 -
SUPPORTS
如果当前有事务,则使用事务;如果当前没有事务,则不适用事务。(对于支持类型的事务,主要用于去做查询。)
举例:领导没饭吃,小明也没饭吃;领导有饭吃,小明也有饭吃。 -
MANDATORY
该传播属性强制必须存在一个事务,如果不存在,则抛出异常。
举例:领导必须管饭,不管饭没饭吃,小明就不乐意了,就不干了(抛出异常)。 -
REQUIRES_NEW
如果当前有事务,则挂起该事务,并且自己创建一个新的事务给自己使用;如果当前没有事务,则同REQUIRED。
领导有饭吃,小明偏不要,小明自己买了自己吃。 -
NOT_SUPPORTS
如果当前有事务,则把事务挂起,自己不使用事务去运行数据库操作。 -
NEVER
如果当前有事务存在,则抛出异常。 -
NESTED
如果当前有事务,则开启子事务(嵌套事务),嵌套事务时独立提交或者回滚的;如果当前没有事务,则同REQUIRED。但是如果主事务提交,则会携带子事务一起提交。如果主事务回滚,子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。
三、Transaction注解事务失效场景
场景一:
在有的博客中这样说到:如果在同一个类中,a方法调用了b方法,然后事务注解只添加在b方法上,当调用a方法时,b方法的事务是不生效的。
以下是验证该说法的例子:
(1)创建测试StuServiceImpl类
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StuMapper stuMapper;
public void saveChildren(){
saveChildren1();
saveChildren2();
}
public void saveChildren1(){
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1);
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren2(){
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2);
int a = 1/0;
Stu stu3 = new Stu();
stu3.setName("child-3");
stu3.setAge(33);
stuMapper.insert(stu3);
}
}
(2)创建测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class TransTest {
@Autowired
private StuService stuService;
@Test
public void myTest2(){
stuService.saveChildren();
}
}
运行结果如下所示。
从运行结果结果中,我们可以看出加在saveChildren2()方法上的事务注解没有生效,它在执行插入child-2后,再执行int a = 1/0时抛出异常,如果事务生效则child-2应该会被回滚。
如果我们把saveChildren2()方法上的事务注解去掉,并在saveChildren()方法上加事务注解。清空数据库表并重新执行测试方法,此时事务生效并回滚saveChildren()插入的数据,则执行结果如下:
- 事务失效原因