Hibernate/Spring Boot:如何从回滚中排除实体



描述

当发生异常时,排除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(..)出于某种原因!注意,已知方法openStatelessSessionsaverollbackcommit等在其他上下文中工作。我把它们复制到这个例子中。我还尝试使用repository来加载实体,但在这里我当然得到了StaleObjectException

我还为一个单独的异常处理程序服务使用了@Transaction(Propagation.REQUIRES_NEW),但它保留了实体管理器中的所有更改,而不仅仅是ProcessEntry实体。你可以提前清理会议,但对我来说,这也冻结了。

这很可能是因为您冻结了数据库,而不是应用程序。您正在尝试插入一条记录,该记录在另一事务完成之前已尝试插入到另一事务中。这是likley将数据库从行锁转换为全表锁。因此,每一笔交易都在等待另一笔交易结束后才能继续进行。

你可以试试其中一种:

1.  Fully roll back and complete the first transaction before trying to redo the insert.   Since you are still in the exception processing logic holding the exception from being thrown Spring Batch will not have rolled back the transaction for that step yet.
2.  Try to do the the extra insert in one of Spring Batch's onError() listeners.
3.  Do the insert into a separate temp table (you can create as the start of the job, and delete at the end of the job.) and use an onComplete() listener for the step to then copy any entries in that temp table to your intended table.

最新更新