如何在捕获 EJB 回滚的 ApplicationException 后避免回滚?

How to avoid Rollbacking after Catching EJB Rollbacked ApplicationException?

我们面临着类似于以下的场景:

@ApplicationException(rollback = true)
class UniqueConstraintViolated extends RuntimeException { ... }

interface GenericStorageService {
  void insert(Object ety); // throws UniqueConstraintViolation
}

class ServiceA {
  @Inject GenericStorageService store;

  void insert(A ety) {
    someSideEffect();
    store.insert(ety);
    someOtherSideEffect();
  }
}

class ServiceB {
  @Inject GenericStorageService store;

  void insertIfNotYetPresent(B ety) {
    try {        
      someSideEffect();
      store.insert(ety);
      someOtherSideEffect();
    } catch (UniqueConstraintViolation e) {
      // that's totally ok
    }
  }
}

在这种情况下,

根据(我的理解)EJB 规范,上述代码在任何一种情况下都会触发 回滚,而不会导致所需的语义。

据我了解,EJB 留给我们以下选项:

  1. rollback = false 修饰 UniqueConstraintViolated,在 ServiceA 中手动捕获它并通过编程事务控制回滚事务。
  2. UniqueConstraintViolated 拆分为两个兄弟姐妹 UniqueConstraintViolatedThatNeedsRollbackUniqueConstraintViolatedThatNeedsNoRollback。此外,用两个变体 insertWithRollbackingUniqueConstraintinsertWithNonRollbackingUniqueConstraint.
  3. 替换 GenericStorageServiceinsert 方法
  4. 吸一下就好了。

选项 1 不可取,因为大多数服务的类型与ServiceA 相同,因此 rollback = true 是更准确的选择。此外,它还破坏了声明式事务控制的优雅。

选项 2 不可取,因为GenericStorageService,这两种情况实际上是一样的。在这个层面上区分是没有意义的。此外,UniqueConstraintViolated 并不是唯一需要区分的例外……我们会遭受组合爆炸的困扰。

选项 3 无需进一步解释。

最后一个问题是:

选项 4 是什么?

对于选项 2,这通常是我的解决方法。

//So generic transaction service, that commits every transaction in a different transaction context.
@Stateless
@TransactionAttribute(REQUIRES_NEW)
public class TransactionalService {

   public void executeTransactional(final Runnable task) {
     task.run();
   }
}

@Statless
public class ServiceB {

  @Inject GenericStorageService store;
  @Inject TransactionalService transactionalService;

  public void insertIfNotYetPresent(B ety) {
    try {       
      transactionalService.executeTransactional(new Runnable() {
         public void run() {
            store.insert(ety);
         }
      };

      transactionalService.executeTransactional(new Runnable() {
         public void run() {
            someSideEffect();
         }
      };

    } catch (UniqueConstraintViolation e) {
      // that's totally ok
    }
  }
}

//如果你在java8,非常简单,所有的冗长都没有了

@Statless
public class ServiceB {

  @Inject GenericStorageService store;
  @Inject TransactionalService transactionalService;

  public void insertIfNotYetPresent(B ety) {
    try {   
      transactionalService.executeTransactional(() -> store.insert(ety) ); 
      transactionalService.executeTransactional(() -> someSideEffect() );

    } catch (UniqueConstraintViolation e) {
      // that's totally ok
    }
  }
}