【问题标题】:Force a transactional rollback without encountering an exception?强制事务回滚而不遇到异常?
【发布时间】:2009-05-07 00:11:24
【问题描述】:

我有一个方法可以做很多事情;其中包括许多插入和更新。

它是这样声明的:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public int saveAll() {
 //do stuff;
}

它完全按照预期工作,我对此没有任何问题。然而,在某些情况下,尽管没有异常,但我想强制回滚......目前,当我遇到正确的条件时,我正在强制异常,但这很丑陋,我不喜欢它。

我可以以某种方式主动调用回滚吗?

异常调用它...我想也许我也可以。

【问题讨论】:

标签: java hibernate spring


【解决方案1】:

在 Spring Transactions 中,您使用 TransactionStatus.setRollbackOnly()

您在这里遇到的问题是您使用@Transactional 来划分您的交易。这具有非侵入性的好处,但这也意味着如果您想手动与事务上下文交互,则不能。

如果您想严格控制事务状态,则必须使用程序化事务而不是声明性注释。这意味着使用 Spring 的 TransactionTemplate,或者直接使用它的 PlatformTransactionManager。请参阅 Spring 参考手册的第 9.6 节。

使用TransactionTemplate,您提供了一个实现TransactionCallback 的回调对象,并且此回调中的代码可以访问TransactionStatus 对象。

它不如@Transactional 好,但您可以更密切地控制您的 tx 状态。

【讨论】:

  • 在使用 @Transactional 的方法中间,使用 TransactionInterceptor.currentTransactionStatus().setRollbackOnly() 是否会是一种手动与事务交互的方式?主要用于替换setRollbackOnly EJB 方法。如果您对此发表评论,将会很感兴趣。谢谢!
【解决方案2】:

这对我有用:

TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

【讨论】:

    【解决方案3】:

    我们不使用 EJB,而是使用简单的 Spring,我们选择了 AOP 方法。 我们实现了新的注解@TransactionalWithRollback 并使用AOP 用“around”通知来包装那些注解的方法。为了实施我们使用提到的TransactionTemplate 的建议。这意味着一开始需要做一些工作,但结果我们可以像在其他情况下使用 @Transactional 一样使用 @TransactionalWithRollback 注释方法。主代码看起来干净简洁。

    //
    // Service class - looks nice
    //
    class MyServiceImpl implements MyService {
        @TransactionalWithRollback
        public int serviceMethod {
            // DO "read only" WORK
        }
    }
    

    //
    // Annotation definition
    //
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface TransactionalWithRollback {
    }
    

    //
    // the around advice implementation
    //
    public class TransactionalWithRollbackInterceptor {
        private TransactionTemplate txTemplate;
        @Autowired private void setTransactionManager(PlatformTransactionManager txMan) {
            txTemplate = new TransactionTemplate(txMan);
        }
    
        public Object doInTransactionWithRollback(final ProceedingJoinPoint pjp) throws Throwable {
            return txTemplate.execute(new TransactionCallback<Object>() {
                @Override public Object doInTransaction(TransactionStatus status) {
                    status.setRollbackOnly();
                    try {
                        return pjp.proceed();
                    } catch(RuntimeException e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }
    

    //
    // snippet from applicationContext.xml:
    //
    <bean id="txWithRollbackInterceptor" class="net.gmc.planner.aop.TransactionalWithRollbackInterceptor" />
    
    <aop:config>
        <aop:aspect id="txWithRollbackAspect" ref="txWithRollbackInterceptor">
            <aop:pointcut 
                id="servicesWithTxWithRollbackAnnotation" 
                expression="execution( * org.projectx..*.*(..) ) and @annotation(org.projectx.aop.TransactionalWithRollback)"/>
            <aop:around method="doInTransactionWithRollback" pointcut-ref="servicesWithTxWithRollbackAnnotation"/>
        </aop:aspect>
    </aop:config>
    

    【讨论】:

    • +1 花时间回答了我 4 年前提出的一个问题,该问题已经获得了多个赞成的答案! :)
    • 我喜欢完整的东西:-)
    • 对于这种情况,您可能应该使用@Transactional(readOnly=true)
    【解决方案4】:

    如果您在 EJB 中,请在 SessionContext 上致电 setRollbackOnly()

    你可以像这样注入SessionContext

    public MyClass {
        @Resource
        private SessionContext sessionContext;
    
        @Transactional(propagation = Propagation.REQUIRED, 
                       isolation = Isolation.DEFAULT, 
                       readOnly = false)
        public int saveAll(){
            //do stuff;
            if(oops == true) {
                 sessionContext.setRollbackOnly();
                 return;
            }
        }
    

    setRollbackOnly()EJBContext 的成员。 SessionContext 扩展 EJBContext: http://java.sun.com/j2ee/1.4/docs/api/javax/ejb/SessionContext.html 请注意,它仅在会话 EJB 中可用。

    @Resource 是一个标准的 Java EE 注释,因此您可能应该检查您在 Eclipse 中的设置。这是一个example,介绍如何使用@Resource 注入SessionContext。

    我怀疑这可能不是您的解决方案,因为您似乎没有使用 EJB — 解释为什么 Eclipse 没有找到 @Resource

    如果是这种情况,那么您将需要直接与事务交互——请参阅事务模板。

    【讨论】:

    • Eclipse 不将@Resource 识别为注解,并且 setRollbackOnly() 不是 SessionContext 的成员(当它从 org.springframework.orm.hibernate3.SpringSessionContext 导入时)。所以...我更近了,但还不够近:)
    【解决方案5】:

    您应该让 spring 注入事务管理器。然后你就可以调用它的回滚方法了。

    【讨论】:

    • 这似乎是正确的方法...我现在正在调查 SavepointManager。
    • 这会导致最后抛出异常“事务回滚,因为它已被标记为仅回滚”
    【解决方案6】:

    我有用@Transactional 注释的服务方法。当验证失败并且我已经有一个实体附加到当前工作单元时,我使用sessionFactory.getCurrentSession().evict(entity) 来确保没有任何内容写入数据库。这样我就不需要抛出异常了。

    【讨论】:

    • 这可能是长时间会话中的折衷解决方案。
    【解决方案7】:

    目前,当我遇到正确的时候,我正在强制异常 条件,但它很丑,我不喜欢它。

    为什么丑?否则我会争辩。您的公共方法上有 @TransactionalrollbackFor 属性如下:

    @Transactional(rollbackFor = Exception.class)
    public void myMethod() throws IllegalStateException {
    

    然后如果你的方法变南了,你可能会抛出一个标准的 Java 异常:

    throw new IllegalStateException("XXX");
    

    这很漂亮,您使用现有的标准 Java 异常,仅使用 1-liner,框架执行回滚,仅此而已。

    【讨论】:

      【解决方案8】:

      是的,我们可以在使用@Transactional(类级别)时强制回滚而不会遇到异常。我们可以简单地抛出一个异常(任何合适的)。喜欢

      if(some condition matches){
       throw new DataIntegrityViolationException("Rollback Tnx.. Since ..." );
      } 
      

      【讨论】:

      • We can simply throw an exception (any suitable one) 实际上 Spring 的默认设置是必须是 RuntimeException 才能回滚
      【解决方案9】:

      抛出异常并按设计使用框架,否则不要使用声明性事务管理并遵循上述 skaffman 建议。保持简单。

      【讨论】:

        猜你喜欢
        • 2013-10-16
        • 1970-01-01
        • 2012-12-28
        • 2018-08-09
        • 1970-01-01
        • 2016-08-14
        • 1970-01-01
        • 1970-01-01
        • 2020-09-24
        相关资源
        最近更新 更多