질문입니다. 평소대로 서비스에
@Transactional
주석을 추가했습니다. 데이터베이스를 쿼리할 때 왜 여전히 데이터 불일치가 발견됩니까? 트랜잭션이 작동하지 않고 데이터가 일치하지 않기 때문인 것 같습니다. 롤백이 발생하면 비정상입니다. 그래서 해당 코드에 대해 몇 가지 테스트를 해본 결과 두 개의 함정에 빠졌다는 사실을 발견했습니다. 이는 실제로 트랜잭션이 롤백되지 않아 발생한 데이터 불일치였습니다. 아래에서 배운 내용을 요약해 보겠습니다.@Transactional
,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。下面总结一下经验教训:
编程式的事务管理
实际应用中很少使用
通过使用TransactionTemplate
手动管理事务
声明式的事务管理
开发中推荐使用(代码侵入最少)
Spring的声明式事务是通过AOP实现的
主要掌握声明式的事务管理。
总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。
大概就是 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 会提交或回滚事务,大致流程如下图:
总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。
在一段业务逻辑中对数据库异常进行了处理,使用了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官网。
上一节中介绍了如何设置开启Spring事务,一般在你的应用的Service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。
在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。
在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked
프로그래밍 방식 트랜잭션 관리
TransactionTemplate
<advice> <attributes> <method></method> <method></method> </attributes> </advice>🎜🎜단위 테스트 코드는 다음과 같습니다. 🎜🎜
@Transactional(rollbackForClassName={"Exception"}) 或者 @Transactional(rollbackFor={Exception.class})🎜🎜이전 섹션에서 볼 수 있듯이 선언적 트랜잭션은 AOP 동적 프록시를 통해 구현되며, 이는 프록시 클래스를 생성합니다. 트랜잭션 관리 및 대상 클래스(서비스) 자체는 프록시 클래스의 존재를 인식할 수 없습니다. 🎜🎜@Transactional이라는 어노테이션이 붙은 메소드의 경우, 프록시 클래스의 메소드를 호출하면 인터셉터인 TransactionInterceptor를 통해 트랜잭션이 시작되고, 마지막으로 호출이 완료된 후 대상 클래스의 메소드가 호출됩니다. 커밋 또는 롤백합니다. 트랜잭션의 일반적인 프로세스는 다음과 같습니다. 🎜🎜
<advice> <attributes> <method></method> <method></method> </attributes> </advice>🎜🎜BizException의 정의는 다음과 같습니다. 🎜🎜
@Transactional(noRollbackForClassName={"InstrumentNotFoundException"}) 或者 @Transactional(noRollbackFor={InstrumentNotFoundException.class})🎜🎜위 코드의 선언적 트랜잭션은 예외가 발생해도 롤백되지 않습니다. . 코드에서 예외를 포착했지만 동시에 예외도 발생시켰습니다. 왜 트랜잭션이 롤백되지 않았나요? 예외 유형이 잘못된 것이라고 추측하여 원인을 찾기 시작했고 Spring 공식 문서를 살펴보며 답을 찾았습니다. 다음은 Spring 공식 웹사이트에서 번역된 내용입니다. 🎜
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!