春のトランザクション障害シナリオ

特定の障害シナリオ:

  1. 注釈の@Transactional構成方法は、公的な許可によって変更されることはありません。
  2. アノテーションが配置@TransactionalされているクラスのSpringコンテナによって管理されていないBean 。
  3. アノテーション@Transactionalが配置されているクラスでは、アノテーションによって変更されたメソッドは、クラス内のメソッドによって呼び出されます。
  4. ビジネスコードが間違ったタイプの例外をスローしRuntimeException、トランザクションが失敗します。
  5. ビジネスコードに例外がある場合は、try…catch…ステートメントブロックを使用してキャプチャし、catchステートメントブロックに例外はありませんthrow new RuntimeExecption;(トラブルシューティングが最も難しく、無視しやすい)
  6. 注釈の属性値@Transactionalが正しく設定されていません。つまり、 (この種の伝播メカニズムは通常設定されていません)PropagationPropagation.NOT_SUPPORTED
  7. MySQLリレーショナルデータベースであり、ストレージエンジンがInnoDBではなくMyISAMである場合、トランザクションは機能しません(基本的な開発では発生しません)。上記のシナリオに基づいて、Xiyuanは友人に詳細に説明します。

非公開の許可の変更

Springの公式ドキュメントの紹介を参照すると、要約と翻訳は次のとおりです。

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protectedprivate or package-
visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
译文
使用代理时,您应该只将@Transactional注释应用于具有公共可见性的方法。如果使用@Transactional注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。
复制代码

つまり@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。、現在、@ Transactionalアノテーションが非公開メソッドに作用する場合、図に示すように、コンパイラーは明らかなヒントも提供します。

图片

春以外のコンテナ管理のBean

この種の失敗シナリオに基づくと、経験豊富な上司には基本的にそのようなエラーはありません。@Service アノテーションアノテーション、StudentServiceImplクラスはSpringコンテナによって管理されないため、メソッドに@Transactionalアノテーションが付けられても、トランザクションは有効になりません。

簡単な例は次のとおりです。


//@Service 
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private ClassService classService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClassByException(StudentDo studentDo) throws CustomException {
        studentMapper.insertStudent(studentDo);
        throw new CustomException();
    }
}
复制代码

アノテーション付きメソッドは、クラス内のメソッドによって呼び出されます

この種の障害シナリオは、私たちの日常の開発で最も一般的な場所です。クラスAにメソッドaとメソッドbがあり、メソッドレベルのトランザクションがメソッドbに@Transactionalで追加され、メソッドbがメソッドaで呼び出されます。メソッドbのトランザクションは有効になりません。

原因:Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

@Service
public class ClassServiceImpl implements ClassService {

    @Autowired
    private ClassMapper classMapper;

    public void insertClass(ClassDo classDo) throws CustomException {
        insertClassByException(classDo);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new RuntimeException();
    }
}

//测试用例:
@Test
    public void insertInnerExceptionTest() throws CustomException {
       classDo.setClassId(2);
       classDo.setClassName("java_2");
       classDo.setClassNo("java_2");

       classService.insertClass(classDo);
    }
复制代码

//测试结果:

java.lang.RuntimeException
 at com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:34)
 at com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:27)
 at com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke(<generated>)
复制代码

虽然业务代码报错了,但是数据库中已经成功插入数据,事务并未生效 ;

图片

解决方案:类内部使用其代理类调用事务方法:以上方法略作改动

public void insertClass(ClassDo classDo) throws CustomException {
        //insertClassByException(classDo);
        ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
    }
测试用例:
 @Test
    public void insertInnerExceptionTest() throws CustomException {
       classDo.setClassId(3);
       classDo.setClassName("java_3");
       classDo.setClassNo("java_3");

       classService.insertClass(classDo);
    }
复制代码

业务代码抛出异常,数据库未插入新数据,达到我们的目的,成功解决一个事务失效问题;

图片

数据库数据未发生改变;

图片

注意 :一定要注意启动类上要添加@EnableAspectJAutoProxy(exposeProxy = true)注解,否则启动报错:

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

 at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)
 at com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:28)
复制代码

异常类型非RuntimeException

这种事务失效场景也是非常难排查问题的,如果没有深究源码实现,估计要花费一番功夫啦;

@Service
public class ClassServiceImpl implements ClassService {

    @Autowired
    private ClassMapper classMapper;

    //@Override
    //@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) throws Exception {
        //即使此处使用代理对象调用内部事务方法,数据依然未发生回滚,事务机制亦然失效
        ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertClassByException(ClassDo classDo) throws Exception {
        classMapper.insertClass(classDo);
        //抛出非RuntimeException类型
        throw new Exception();
    }
测试用例:
 @Test
    public void insertInnerExceptionTest() throws Exception {
       classDo.setClassId(3);
       classDo.setClassName("java_3");
       classDo.setClassNo("java_3");

       classService.insertClass(classDo);
    }
}
复制代码

运行结果:业务代码抛出异常,但是数据库发生更新操作;

java.lang.Exception
 at com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:35)
 at com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke(<generated>)
 at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
复制代码

数据库依然插入数据,不是我们想要的结果。

图片

解决方案:

@Transactional注解修饰的方法,加上rollbackfor属性值,指定回滚异常类型:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

@Override
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws Exception {
        classMapper.insertClass(classDo);
        throw new Exception();
    }
复制代码

捕获异常后,却未抛出异常

在事务方法中使用try-catch,导致异常无法抛出,自然会导致事务失效。

@Service
public class ClassServiceImpl implements ClassService {

    @Autowired
    private ClassMapper classMapper;

    //@Override
    public void insertClass(ClassDo classDo) {
        ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 测试用例:
 @Test
    public void insertInnerExceptionTest() {
       classDo.setClassId(4);
       classDo.setClassName("java_4");
       classDo.setClassNo("java_4");

       classService.insertClass(classDo);
    }
复制代码

执行结果:

图片

图片

解决方案:捕获异常并抛出异常

 @Override
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
复制代码

事务传播行为设置异常

此种事务传播行为不是特殊自定义设置,基本上不会使用Propagation.NOT_SUPPORTED,不支持事务

 @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
复制代码

数据库存储引擎不支持事务

以MySQL关系型数据为例,如果其存储引擎设置为 MyISAM,则事务失效,因为MyISMA 引擎是不支持事务操作的;

故若要事务生效,则需要设置存储引擎为InnoDB ;目前 MySQL 从5.5.5版本开始默认存储引擎是:InnoDB;

在并行流paralletStream中进行Spring事务管理?(了解)

只有请求的main线程会被事务进行管理,而其它线程并不会被事务进行管理。

原因:SqlSession的不同导致的。

おすすめ

転載: juejin.im/post/7078159722784210981