Spring 事务管理

事务

Github自动化测试的缘由,(最后找到的缘由是getOneSavedDepartment时,这个Department没存上,因此ToMessage引用了一个未持久化的Department,就报错了),特此学习了一下事务。java

事务,基础概念就不说了。spring

Spring为咱们提供了对事务的支持,咱们只须要很简单的注解或者XML配置便可实现。ide

去网上找了好多篇关于Spring事务的博客,全是字,根本没有心情去看,更谈不上深刻理解了,做者还在标题中自认为本身讲的比较好。学习

clipboard.png

若是你也不喜欢大段的文字,请继续向下看,我保证我画的图不会让你失望。测试

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 {};
}

事务注解中有好多的属性,若是是简单场景的话,那咱们只须要默认地配置就行了。spa

可是若是应用发嵌套事务的复杂场景下,咱们就须要研究研究这几个配置项了。3d

这里咱们深刻学习一下事务的传播属性propagationcode

Propagation

REQUIRED

由于咱们的事务传播级别就是REQUIRED,因此咱们不配置propagation来测试REQUIREDblog

若是当前存在事务,则使用当前事务。若是不存在任何事务,则建立一个新的事务。

内部方法开启默认REQUIRED级别的事务

一个🐱,一个🐩,省略setget方法。事务

@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);
}

没有任何异常抛出,猫存上了,狗也存上了。

clipboard.png

clipboard.png

将保存狗的方法由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);
}

clipboard.png

clipboard.png

猫存上了,狗没存上。由于同一事务的全部操做是同时成功,同时失败的。因此咱们判定,猫和狗用的是两个事务。

图解

clipboard.png

若是不存在任何事务,则建立一个新的事务。

save猫save狗都是默认的REQUIRED级别,test方法未开启事务,因此当前不存在任何事务。

因此save猫建立了一个新的事务,save狗也建立了一个新的事务。

clipboard.png

外部方法开启事务

为外部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);
}

显然,未抛出异常,都存上了。

clipboard.png

clipboard.png

方法修改成保存并抛出异常。

@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猫是有影响的,猜想两者用的是同一个事务。

clipboard.png

clipboard.png

若是当前存在事务,则使用当前事务。

clipboard.png

若是捕获抛出的RuntimeException,该方法仍然不能保存。

Dog dog = new Dog();
dog.setName("史努比");
try {
    dogService.saveThrowException(dog);
} catch (RuntimeException e) {
    System.out.println("error");
}

会抛出异常,该事务不能被提交。

clipboard.png

clipboard.png

clipboard.png

总结:REQUIRED修饰的内部方法会加入外部方法的事务中,其中有任一一个方法抛异常,事务都会回滚。

SUPPORTS

SUPPORTSREQUIRED相似:

若是当前存在事务,则使用当前事务。若是不存在任何事务,则不使用事务。

MANDATORY

MANDATORYREQUIRED相似:

若是当前存在事务,则使用当前事务。若是不存在任何事务,则抛出异常。

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();
}

clipboard.png

clipboard.png

NOT_SUPPORTED

若是当前存在事务,则挂起当前事务。同时以非事务方式运行。

NEVER

永远不要存在事务。若是当前存在事务,则抛出异常。

NESTED

若是当前存在事务,则在当前事务的一个嵌套事务中运行。

该级别只对DataSourceTransactionManager事务管理器生效。

原本想测试一下的,惋惜。

clipboard.png

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

NestedTransactionNotSupportedException,不支持嵌套事务。

StackOverflow上的回答,Hibernate不支持嵌套事务。

clipboard.png

总结

  1. 哥仨REQUIREDSUPPORTSMANDATORY,若是当前存在事务,则使用当前事务。
  2. 哥仨REQUIRES_NEWNOT_SUPPORTEDNEVER都不支持当前存在的事务。
  3. NESTED,嵌套事务,以为这个应该是事务中最好用且最合理的,惋惜Hibernate不支持。
相关文章
相关标签/搜索