我在 @Transactional 方法中提交事务时遇到问题:
methodA() {
methodB()
}
@Transactional
methodB() {
...
em.persist();
...
em.flush();
log("OK");
}
当我从 methodA() 调用 methodB() 时,该方法成功通过,我可以在日志中看到“OK”。但后来我得到
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at methodA()...
异常中完全缺少methodB的上下文-我想这可以吗? methodB() 中的某些内容仅将事务标记为回滚?我怎样才能找到它?例如,有没有办法检查 getCurrentTransaction().isRollbackOnly() 之类的东西? - 像这样我可以逐步完成该方法并找到原因。
当您将方法标记为 @Transactional
时,方法内发生的任何异常都会将周围的 TX 标记为仅回滚(即使您捕获它们)。您可以使用 @Transactional
注释的其他属性来防止它回滚,例如:
@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
我终于明白了这个问题:
methodA() {
methodB()
}
@Transactional(noRollbackFor = Exception.class)
methodB() {
...
try {
methodC()
} catch (...) {...}
log("OK");
}
@Transactional
methodC() {
throw new ...();
}
发生的情况是,即使 methodB
具有正确的注释,methodC
也没有。当抛出异常时,第二个 @Transactional
无论如何都将第一个事务标记为 Rollback。
propagation=requires_new
那么 methodB 不会回滚?
methodC
必须在不同的 Spring bean/服务中,或者通过 Spring 代理以某种方式访问。否则 Spring 将不可能知道您的异常。只有通过 @Transactional
注释的异常才能将事务标记为仅回滚。
@Transactional
的传播是 REQUIRED
(被视为“在现有事务中执行,如果有的话”)
要快速获取导致的异常而不需要重新编码或重建,请设置断点
org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3
并在堆栈中向上,通常到某个拦截器。在那里,您可以从某个 catch 块中读取导致的异常。
org.hibernate.jpa.internal.TransactionImpl
org.hibernate.engine.transaction.internal.TransactionImpl
,方法是 setRollbackOnly
。
org.hibernate.jpa.internal.TransactionImpl.setRollbackOnly
Hibernate 5.0.12 的方法
我在运行我的应用程序时遇到了这个异常。
最后问题出在sql查询上。我的意思是查询是错误的。
请验证您的查询。这是我的建议
找到了一个很好的解决方案解释:https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/
1) 如果它不需要事务控制,则从嵌套方法中删除 @Transacional。因此,即使它有异常,它也会冒泡并且不会影响事务性内容。
或者:
2)如果嵌套方法确实需要事务控制,则将其设置为传播策略的REQUIRE_NEW,这样即使抛出异常并仅标记为回滚,调用者也不会受到影响。
查找代码的 ...
部分中引发和捕获的异常。运行时和回滚应用程序异常在从业务方法中抛出时会导致回滚,即使在其他地方捕获也是如此。
您可以使用上下文来确定事务是否标记为回滚。
@Resource
private SessionContext context;
context.getRollbackOnly();
SessionContext
是 Spring 中的标准类吗?在我看来,它更像是 EJB3,它不包含在我的 Spring 应用程序中。
TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()
的东西可用。
嵌套方法回滚总是有原因的。如果您没有看到原因,您需要将您的记录器级别更改为调试,您将在其中看到事务失败的更多详细信息。我通过添加更改了我的 logback.xml
<logger name="org.springframework.transaction" level="debug"/>
<logger name="org.springframework.orm.jpa" level="debug"/>
然后我在日志中得到了这一行:
Participating transaction failed - marking existing transaction as rollback-only
所以我只是单步执行我的代码以查看生成此行的位置,发现有一个 catch 块没有抛出任何东西。
private Student add(Student s) {
try {
Student retval = studentRepository.save(s);
return retval;
} catch (Exception e) {
}
return null;
}
在 Bean.xml 中禁用事务管理器
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
注释掉这些行,你会看到导致回滚的异常;)
在 productRepository 中应用以下代码
@Query("update Product set prodName=:name where prodId=:id ") @Transactional @Modifying int updateMyData(@Param("name")String name, @Param("id") Integer id);
而在junit测试中应用下面的代码
@Test
public void updateData()
{
int i=productRepository.updateMyData("Iphone",102);
System.out.println("successfully updated ... ");
assertTrue(i!=0);
}
它适用于我的代码
不定期副业成功案例分享
noRollbackFor=Exception.class
,但它似乎没有效果——它是否适用于继承的异常?methodC
)。methodB
和methodC
都使用相同的 TX,并且始终使用最具体的@Transactional
注释,因此当methodC
抛出异常时,周围的 TX 将被标记为仅回滚。您还可以使用不同的传播标记来防止这种情况。EmptyResultDataAccessException
异常,我得到了同样的错误。将我的注释更改为@Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)
解决了这个问题。@Transactional
代理包装器的异常,即 uncaught。有关完整故事,请参阅 Vojtěch 的其他答案。可能有嵌套的@Transactional
方法可以将您的事务标记为仅回滚。noRollbackFor
仅在globalRollbackOnParticipationFailure=false
时有效