>  기사  >  Java  >  Spring 트랜잭션 관리 작업 방법

Spring 트랜잭션 관리 작업 방법

PHP中文网
PHP中文网원래의
2017-06-20 16:20:291739검색

질문입니다. 평소대로 서비스에 @Transactional 주석을 추가했습니다. 데이터베이스를 쿼리할 때 왜 여전히 데이터 불일치가 발견됩니까? 트랜잭션이 작동하지 않고 데이터가 일치하지 않기 때문인 것 같습니다. 롤백이 발생하면 비정상입니다. 그래서 해당 코드에 대해 몇 가지 테스트를 해본 결과 두 개의 함정에 빠졌다는 사실을 발견했습니다. 이는 실제로 트랜잭션이 롤백되지 않아 발생한 데이터 불일치였습니다. 아래에서 배운 내용을 요약해 보겠습니다. @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。下面总结一下经验教训:

Spring事务的管理操作方法

  • 编程式的事务管理

    • 实际应用中很少使用

    • 通过使用TransactionTemplate 手动管理事务

  • 声明式的事务管理

    • 开发中推荐使用(代码侵入最少)

    • Spring的声明式事务是通过AOP实现的

主要掌握声明式的事务管理。

spring事务不回滚的两个原因

总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。

1. Service类内部方法调用

大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:

@Servicepublic class RabbitServiceImpl implements RabbitService {

    @Autowiredprivate RabbitDao rabbitDao;
    @Autowiredprivate TortoiseDao tortoiseDao;

    @Overridepublic Rabbit methodA(String name){return methodB(name);
    }

    @Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name){
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);return true;
    }

}

单元测试代码如下:

public class RabbitServiceImplTest {

    @Autowiredprivate RabbitService rabbitService;// 事务未开启    @Testpublic void testA(){
        rabbitService.methodA("rabbit");
    }// 事务开启    @Testpublic void testB(){
        rabbitService.methodB("rabbit");
    }
}

从上一节中可以看到,声明式事务是通通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。

对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图:

Spring 트랜잭션 관리 작업 방법

总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。

2. try...catch异常

在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:

@Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name) throws BizException {try {
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);
    } catch (Exception e) {throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg);
    }return true;
}

BizException的定义如下:

public class BizException extends Exception {// 自定义异常}

上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了Spring的官方文档,找到了答案。下面是翻译自Spring官网。

17.5.3 声明式事务的回滚

上一节中介绍了如何设置开启Spring事务,一般在你的应用的Service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。

在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。

  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked

    Spring 트랜잭션 관리 작업 방법

프로그래밍 방식 트랜잭션 관리

      실제 애플리케이션에서는 거의 사용되지 않음
    • TransactionTemplate

    선언적 트랜잭션 관리
      🎜🎜개발 권장(최소 코드 침입)🎜🎜🎜🎜Spring의 선언적 트랜잭션은 AOP를 통해 구현됩니다🎜🎜🎜🎜🎜선언적 스타일 트랜잭션 관리를 주로 마스터하세요. 🎜

      스프링 트랜잭션이 롤백되지 않는 두 가지 이유

      🎜 트랜잭션이 롤백되지 않는 두 가지 이유를 요약해 보겠습니다. 하나는 Service 클래스의 내부 메소드 호출이고, 다른 하나는 try... 예외를 잡아라. 🎜
      1. 서비스 클래스의 내부 메소드 호출
      🎜 아마도 내부적으로 메소드 B를 호출하는 메소드 A가 있을 것입니다. 메소드 A는 트랜잭션 관리 기능이 없고 메소드 B는 선언적 트랜잭션을 사용합니다. 트랜잭션 관리를 위한 트랜잭션 주석 메소드에 대해 설명합니다. 샘플 코드는 다음과 같습니다. 🎜🎜
    <advice>
      <attributes>
      <method></method>
      <method></method>
      </attributes>
    </advice>
    🎜🎜단위 테스트 코드는 다음과 같습니다. 🎜🎜
    @Transactional(rollbackForClassName={"Exception"})
    或者
    @Transactional(rollbackFor={Exception.class})
    🎜🎜이전 섹션에서 볼 수 있듯이 선언적 트랜잭션은 AOP 동적 프록시를 통해 구현되며, 이는 프록시 클래스를 생성합니다. 트랜잭션 관리 및 대상 클래스(서비스) 자체는 프록시 클래스의 존재를 인식할 수 없습니다. 🎜🎜@Transactional이라는 어노테이션이 붙은 메소드의 경우, 프록시 클래스의 메소드를 호출하면 인터셉터인 TransactionInterceptor를 통해 트랜잭션이 시작되고, 마지막으로 호출이 완료된 후 대상 클래스의 메소드가 호출됩니다. 커밋 또는 롤백합니다. 트랜잭션의 일반적인 프로세스는 다음과 같습니다. 🎜🎜 🎜🎜요약하자면, 메소드 A에서 메소드 B를 호출하면 실제로 Spring 컨텍스트를 통해 얻은 프록시 클래스 대신 "this"의 참조를 통해 대상 클래스의 메소드를 호출하므로 트랜잭션이 시작되지 않습니다. 🎜
    2. try...catch 예외
    🎜 데이터베이스 예외는 try...catch 절을 사용하여 예외를 캡처하고 사용자 지정 예외를 발생시키는 비즈니스 논리로 처리됩니다. 샘플 코드는 다음과 같습니다. 🎜🎜
    <advice>
      <attributes>
      <method></method>
      <method></method>
      </attributes>
    </advice>
    🎜🎜BizException의 정의는 다음과 같습니다. 🎜🎜
    @Transactional(noRollbackForClassName={"InstrumentNotFoundException"})
    或者
    @Transactional(noRollbackFor={InstrumentNotFoundException.class})
    🎜🎜위 코드의 선언적 트랜잭션은 예외가 발생해도 롤백되지 않습니다. . 코드에서 예외를 포착했지만 동시에 예외도 발생시켰습니다. 왜 트랜잭션이 롤백되지 않았나요? 예외 유형이 잘못된 것이라고 추측하여 원인을 찾기 시작했고 Spring 공식 문서를 살펴보며 답을 찾았습니다. 다음은 Spring 공식 웹사이트에서 번역된 내용입니다. 🎜
    17.5.3 선언적 트랜잭션 롤백
    🎜이전 섹션에서는 Spring 트랜잭션을 설정하고 활성화하는 방법을 소개했습니다. 이는 일반적으로 애플리케이션의 서비스 계층 코드에서 설정되며 이 섹션에서는 간단하고 인기 있는 트랜잭션을 소개합니다. 선언적 트랜잭션에서 트랜잭션 롤백을 제어하는 ​​방법. 🎜🎜Spring FrameWork 트랜잭션 프레임워크에서 권장되는 트랜잭션 롤백 방법은 현재 실행 중인 트랜잭션 컨텍스트에서 예외를 발생시키는 것입니다. 예외가 처리되지 않으면 Spring FrameWork의 트랜잭션 프레임워크 코드는 예외가 호출 스택에 발생할 때 처리되지 않은 예외를 포착한 다음 트랜잭션을 롤백으로 표시할지 여부를 결정합니다. 🎜🎜🎜🎜기본 구성에서 Spring FrameWork의 트랜잭션 프레임워크 코드는 runtime, unchecked 예외가 있는 트랜잭션만 롤백으로 표시합니다. 즉, 트랜잭션에서 발생한 예외는 RuntimeException 또는 Its입니다. 하위 클래스이므로 트랜잭션이 롤백됩니다(오류로 인해 기본적으로 트랜잭션이 롤백되기도 함). 기본 구성을 사용하면 확인된 모든 예외로 인해 트랜잭션 롤백이 발생하지 않습니다. 🎜🎜🎜🎜🎜 참고: 확인되지 않은 예외에는 Error 및 RuntimeException이 포함됩니다. RuntimeException의 모든 하위 클래스도 이 범주에 속합니다. 다른 유형은 Checked Exception입니다. 🎜🎜🎜🎜🎜예외 유형을 정확하게 구성하고 확인된 예외를 포함하여 이 예외 유형에 대한 트랜잭션 롤백을 지정할 수 있습니다. 다음 XML 코드 조각은 트랜잭션 롤백을 유발하도록 확인된 예외를 구성하고 사용자 정의 예외 유형을 적용하는 방법을 보여줍니다. 🎜🎜🎜🎜
    <advice>
      <attributes>
      <method></method>
      </attributes>
    </advice>
    🎜🎜동일한 효과를 갖는 주석 형식은 다음과 같습니다. 🎜🎜
    public void resolvePosition() {  try {      // some business logic...
      } catch (NoProductInStockException ex) {      // trigger rollback programmatically      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }
    }
    🎜
    • 在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException 异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。

    <advice>
      <attributes>
      <method></method>
      <method></method>
      </attributes>
    </advice>

      与其有同样作用的注解形式如下:   

    @Transactional(noRollbackForClassName={"InstrumentNotFoundException"})
    或者
    @Transactional(noRollbackFor={InstrumentNotFoundException.class})
    • 还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException 之外的任何异常都会导致事务回滚。

    <advice>
      <attributes>
      <method></method>
      </attributes>
    </advice>
    • 你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和Spring FrameWork 的事务框架代码紧密的绑定在一起,示例代码如下:

    public void resolvePosition() {  try {      // some business logic...
      } catch (NoProductInStockException ex) {      // trigger rollback programmatically      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }
    }

      如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean POJO-based architecture.(没懂...)

    看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException。我的解决办法是,在注解@Transactional中添加 rollbackFor={BizException.class}。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。

    위 내용은 Spring 트랜잭션 관리 작업 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.