如何在捕获 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
}
}
}
在这种情况下,
- 请求插入之前插入的一些
A
是一个实际的用户错误。无法以有意义的方式提交事务。
- 请求插入以前存在的
B
部分 不是 错误。通过简单地确认所述 B
的存在,可以安全地提交事务。特别是,无论给定的 B
之前是否插入,都需要提交副作用。
根据(我的理解)EJB 规范,上述代码在任何一种情况下都会触发 回滚,而不会导致所需的语义。
据我了解,EJB 留给我们以下选项:
- 用
rollback = false
修饰 UniqueConstraintViolated
,在 ServiceA
中手动捕获它并通过编程事务控制回滚事务。
- 将
UniqueConstraintViolated
拆分为两个兄弟姐妹 UniqueConstraintViolatedThatNeedsRollback
和 UniqueConstraintViolatedThatNeedsNoRollback
。此外,用两个变体 insertWithRollbackingUniqueConstraint
和 insertWithNonRollbackingUniqueConstraint
. 替换 GenericStorageService
的 insert
方法
- 吸一下就好了。
选项 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
}
}
}
我们面临着类似于以下的场景:
@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
}
}
}
在这种情况下,
- 请求插入之前插入的一些
A
是一个实际的用户错误。无法以有意义的方式提交事务。 - 请求插入以前存在的
B
部分 不是 错误。通过简单地确认所述B
的存在,可以安全地提交事务。特别是,无论给定的B
之前是否插入,都需要提交副作用。
根据(我的理解)EJB 规范,上述代码在任何一种情况下都会触发 回滚,而不会导致所需的语义。
据我了解,EJB 留给我们以下选项:
- 用
rollback = false
修饰UniqueConstraintViolated
,在ServiceA
中手动捕获它并通过编程事务控制回滚事务。 - 将
UniqueConstraintViolated
拆分为两个兄弟姐妹UniqueConstraintViolatedThatNeedsRollback
和UniqueConstraintViolatedThatNeedsNoRollback
。此外,用两个变体insertWithRollbackingUniqueConstraint
和insertWithNonRollbackingUniqueConstraint
. 替换 - 吸一下就好了。
GenericStorageService
的 insert
方法
选项 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
}
}
}