事务
因Github
自动化测试的原因,(最后找到的原因是getOneSavedDepartment
时,这个Department
没存上,所以ToMessage
引用了一个未持久化的Department
,就报错了),特此学习了一下事务。
事务,基础概念就不说了。
Spring
为我们提供了对事务的支持,我们只需要很简单的注解或者XML
配置即可实现。
去网上找了好多篇关于Spring
事务的博客,全是字,根本没有心情去看,更谈不上深入理解了,作者还在标题中自认为自己讲的比较好。
如果你也不喜欢大段的文字,请继续向下看,我保证我画的图不会让你失望。
org.springframework.transaction.annotation.Transactional
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
事务注解中有好多的属性,如果是简单场景的话,那我们只需要默认地配置就好了。
但是如果应用发嵌套事务的复杂场景下,我们就需要研究研究这几个配置项了。
这里我们深入学习一下事务的传播属性propagation
。
Propagation
REQUIRED
因为我们的事务传播级别就是REQUIRED
,所以我们不配置propagation
来测试REQUIRED
。
如果当前存在事务,则使用当前事务。如果不存在任何事务,则创建一个新的事务。
内部方法开启默认REQUIRED级别的事务
一个?,一个?,省略set
、get
方法。
@Entity
public class Cat {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
@Entity
public class Dog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
一个猫实现类,一个狗实现类,都有save
方法,并且DogServiceImpl
中还有一个保存并抛出异常的方法,用于测试回滚。省略依赖注入的代码。
@Service
public class CatServiceImpl implements CatService {
@Override
@Transactional
public void save(Cat cat) {
catRepository.save(cat);
}
}
@Service
public class DogServiceImpl implements DogService {
@Override
@Transactional
public void save(Dog dog) {
dogRepository.save(dog);
}
@Override
@Transactional
public void saveThrowException(Dog dog) {
dogRepository.save(dog);
throw new RuntimeException();
}
}
外部方法不开启事务
public void test() {
Cat cat = new Cat();
cat.setName("Hello Kitty!");
catService.save(cat);
Dog dog = new Dog();
dog.setName("史努比");
dogService.save(dog);
}
没有任何异常抛出,猫存上了,狗也存上了。
将保存狗的方法由save
修改为saveThrowException
,抛个异常,测试一下回滚。
public void test() {
Cat cat = new Cat();
cat.setName("Hello Kitty!");
catService.save(cat);
Dog dog = new Dog();
dog.setName("史努比");
dogService.saveThrowException(dog);
}
猫存上了,狗没存上。因为同一事务的所有操作是同时成功,同时失败的。所以我们断定,猫和狗用的是两个事务。
图解
如果不存在任何事务,则创建一个新的事务。
save猫
和save狗
都是默认的REQUIRED
级别,test
方法未开启事务,所以当前不存在任何事务。
所以save猫
创建了一个新的事务,save狗
也创建了一个新的事务。
外部方法开启事务
为外部test
方法添加事务。
@Transactional
public void test() {
Cat cat = new Cat();
cat.setName("Hello Kitty!");
catService.save(cat);
Dog dog = new Dog();
dog.setName("史努比");
dogService.save(dog);
}
显然,未抛出异常,都存上了。
方法修改为保存并抛出异常。
@Transactional
public void test() {
Cat cat = new Cat();
cat.setName("Hello Kitty!");
catService.save(cat);
Dog dog = new Dog();
dog.setName("史努比");
dogService.saveThrowException(dog);
}
两者都没存上,所以断定save狗
的方法抛出的异常对save猫
是有影响的,猜测二者用的是同一个事务。
如果当前存在事务,则使用当前事务。
如果捕获抛出的RuntimeException
,该方法仍然不能保存。
Dog dog = new Dog();
dog.setName("史努比");
try {
dogService.saveThrowException(dog);
} catch (RuntimeException e) {
System.out.println("error");
}
会抛出异常,该事务不能被提交。
总结:REQUIRED
修饰的内部方法会加入外部方法的事务中,其中有任一一个方法抛异常,事务都会回滚。
SUPPORTS
SUPPORTS
与REQUIRED
类似:
如果当前存在事务,则使用当前事务。如果不存在任何事务,则不使用事务。
MANDATORY
MANDATORY
与REQUIRED
类似:
如果当前存在事务,则使用当前事务。如果不存在任何事务,则抛出异常。
REQUIRES_NEW
如果当前存在事务,则挂起当前事务。创建一个新的事务。
应该适合该操作相对独立的情况。就比如下单加支付,如果支付失败,但是这个订单应该还存在。
如果将事务传播级别修改为这个的话,那save狗
如果抛出异常就不影响save猫
了。
@Override
@Transactional
public void test() {
Cat cat = new Cat();
cat.setName("Hello Kitty!");
catService.save(cat);
Dog dog = new Dog();
dog.setName("史努比");
dogService.saveThrowException(dog);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Cat cat) {
catRepository.save(cat);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveThrowException(Dog dog) {
dogRepository.save(dog);
throw new RuntimeException();
}
NOT_SUPPORTED
如果当前存在事务,则挂起当前事务。同时以非事务方式运行。
NEVER
永远不要存在事务。如果当前存在事务,则抛出异常。
NESTED
如果当前存在事务,则在当前事务的一个嵌套事务中运行。
该级别只对DataSourceTransactionManager
事务管理器生效。
本来想测试一下的,可惜。
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
NestedTransactionNotSupportedException
,不支持嵌套事务。
StackOverflow
上的回答,Hibernate
不支持嵌套事务。
总结
- 哥仨
REQUIRED
、SUPPORTS
、MANDATORY
,如果当前存在事务,则使用当前事务。 - 哥仨
REQUIRES_NEW
、NOT_SUPPORTED
、NEVER
都不支持当前存在的事务。 -
NESTED
,嵌套事务,觉得这个应该是事务中最好用且最合理的,可惜Hibernate
不支持。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。