【发布时间】:2022-01-17 16:13:06
【问题描述】:
说明
当异常发生时,排除 Hibernate 托管实体在事务中回滚的最佳方法是什么?
我有一个由 Spring Batch 调用的服务。 Spring Batch 打开并提交了一个我无法控制的事务。在服务过程中,某些实体被更新。但是,当发生异常时,它会传播给调用者,并且所有处理工作都会回滚。这正是我想要的,除了一个特定的实体,它还用于跟踪进程状态以便于监控和其他原因。如果出现错误,我想更新该实体以显示错误状态(在服务类中)。
示例
我试图通过打开一个单独的事务并提交更新来实现这一点:
@Service
@Transactional
public class ProcessService {
@Autowired
private EntityManagerFactory emf;
@Autowired
private ProcessEntryRepository repository;
@Override
public void process(ProcessEntry entry) {
try {
entry.setStatus(ProcessStatus.WORK_IN_PROGRESS);
entry.setTimeWorkInProgress(LocalDateTime.now());
entry = repository.saveAndFlush(entry);
createOrUpdateEntities(entry.getData()); // should all be rolled back in case of exception
entry.setStatus(ProcessStatus.FINISHED);
entry.setTimeFinished(LocalDateTime.now());
repository.saveAndFlush(entry);
} catch (Exception e) {
handleException(entry, e); // <- does not work as expected
throw e; // hard requirement to rethrow here
}
}
private void handleException(ProcessEntry entry, Exception exception) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
StatelessSession statelessSession = null;
Transaction transaction = null;
try {
statelessSession = openStatelessSession();
transaction = statelessSession.beginTransaction();
ProcessEntry entryUnwrap = unwrapHibernateProxy(entry); // to get actual Class
@SuppressWarnings("unchecked")
ProcessEntry entryInSession = (ProcessEntry) statelessSession.get(entryUnwrap.getClass(), entryUnwrap.getId());
entryInSession.setStatus(ProcessStatus.FAILED);
entryInSession.setTimeFailed(LocalDateTime.now());
entryInSession.setStatusMessage(exception.getMessage());
save(transaction, entryInSession);
commit(statelessSession, transaction);
catch (Exception e) {
LOGGER.error("Exception occurred while setting FAILED status on: " + entry, e);
rollback(transaction);
} finally {
closeStatelessSession(statelessSession);
}
}
public StatelessSession openStatelessSession() {
EntityManagerFactory entityManagerFactory = emf;
if (emf instanceof MultipleEntityManagerFactorySwitcher) {
entityManagerFactory = ((MultipleEntityManagerFactorySwitcher) emf).currentFactory();
}
return ((HibernateEntityManagerFactory) entityManagerFactory).getSessionFactory()
.openStatelessSession();
}
public static Long save(StatelessSession statelessSession, ProcessEntry entity) {
if (!entity.isPersisted()) {
return (Long) statelessSession.insert(entity.getClass().getName(), entity);
}
statelessSession.update(entity.getClass().getName(), entity);
return entity.getId();
}
public static void commit(StatelessSession statelessSession, Transaction transaction) {
((TransactionContext) statelessSession).managedFlush();
transaction.commit();
}
public static void rollback(Transaction transaction) {
if (transaction != null) {
transaction.rollback();
}
}
public static void closeStatelessSession(StatelessSession statelessSession) {
if (statelessSession != null) {
statelessSession.close();
}
}
}
但是,由于某种原因,这种方法在save -> statelessSession.update(..) 上完全冻结了应用程序(或者更确切地说,批处理作业永远不会完成)!
请注意,已知方法openStatelessSession、save、rollback、commit 等可以在其他上下文中工作。我将它们复制到这个例子中。
我还尝试使用repository 加载实体,但这里当然得到StaleObjectException。
我还使用了@Transaction(Propagation.REQUIRES_NEW) 来获得一个单独的异常处理程序服务,但这会保留实体管理器中的所有更改,而不仅仅是ProcessEntry 实体。您可以事先清除会话,但在我的情况下也冻结了。
【问题讨论】:
标签: java oracle hibernate transactions spring-batch