代码之家  ›  专栏  ›  技术社区  ›  Yoni

UnexpectedRollbackException重写我自己的异常

  •  5
  • Yoni  · 技术社区  · 16 年前

    我对Spring的事务管理有以下奇怪的场景:

    我有方法A调用方法B调用方法C,每个方法都在不同的类中。方法B和C都包含在事务中。两者都需要传播,所以当Spring创建两个逻辑事务时,数据库中有一个物理事务。

    现在,在方法C中,我抛出了一个RuntimeException。这将内部逻辑事务设置为RollbackOnly和物理事务。在方法B中,我知道可能存在未预期的回滚异常,因此我不会继续正常提交。我从C捕获异常,并抛出另一个RuntimeException。

    我预计外部RuntimeException将导致对外部事务的回滚,但实际行为如下:

    • 外部事务似乎试图提交,或至少检查其状态,然后它抛出未预期的RollbackException,因为物理事务已标记为RollbackOnly。
    • 在引发该异常之前,它将另一个异常打印到日志中,说明“应用程序异常被提交异常覆盖”。因此,调用方A接收到未预期的RollbackException,而不是B抛出的异常。

    我为它找到了一个解决方法,即只在抛出异常之前将外部事务主动设置为回滚。

    public ModelAndView methodB(HttpServletRequest req, HttpServletResponse resp) {
      try{
        other.methodC();
      } catch (RuntimeException e){
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw new RuntimeException ("outer exception");
      }
      return handleGetRequest(req, resp);
    }
    

    但是,这个解决方案强烈地将代码与事务API结合在一起,我希望避免这种情况。有什么建议吗?

    附笔 这两个事务都是为了在运行时异常时回滚。我没有为异常或类似的情况定义任何回滚

    2 回复  |  直到 16 年前
        1
  •  6
  •   Yoni    16 年前

    我找到了这个问题的原因。结果是,方法B在被包装到事务中之前,使用了基于cglib的代理(使用Spring旧方法,2.0之前的版本)。所以当我从methodb中抛出runtimeexception时,cglib最终会抛出invocationTargetexception,这实际上是一个检查过的异常。

    Spring的事务管理器最终捕获了选中的异常,并尝试提交该事务,而不知道methodb抛出的嵌套运行时异常。一旦我发现了这一点,我就设置了事务包装器来回滚检查过的异常,现在它可以按预期工作了。

        2
  •  5
  •   wax    16 年前

    你可以试着设置 failEarlyOnGlobalRollbackOnly 旗到 true 在你 AbstractPlatformTransactionManager 继承人(继承人) HibernateTransactionManager 例如)。下面是它的描述:

    如果事务被全局标记为仅回滚,则设置是否及早失败。

    默认值为“false”,仅在最外层事务边界处导致意外的回滚异常。打开此标志可在第一次检测到全局仅回滚标记时(即使是从内部事务边界内)导致未预期的回滚异常。注意,从Spring2.0开始,全局仅回滚标记的早期失败行为已经统一:默认情况下,所有事务管理器将只在最外层事务边界处导致未预期的回滚异常。例如,这允许在操作失败后继续单元测试,并且事务将永远不会完成。只有当此标志显式设置为“真”时,所有事务管理器才会更早失败。