[英]Hibernate/Spring Boot: How to exclude an entity from rollback
What is the best way to exclude a Hibernate managed entity from being rolled back in a transaction when an exception occurs?发生异常时,排除 Hibernate 托管实体在事务中回滚的最佳方法是什么?
I have a service that is called by Spring Batch.我有一个由 Spring Batch 调用的服务。 Spring Batch opens and commits a transaction which I don't really have control over.
Spring 批处理打开并提交我无法控制的事务。 In the service process certain entities are updated.
在服务过程中,某些实体被更新。 However when an exception occurs it is propagated to the caller and all the processing work is rolled back.
但是,当发生异常时,它会传播给调用者,并且所有处理工作都会回滚。 That is exactly what I want except for a specific entity that is additionally used to track the process state for easier monitoring and other reasons.
这正是我想要的,除了一个特定的实体,它还用于跟踪进程 state 以便于监控和其他原因。 In case of an error I want to update that entity to display the error status (within the service class).
如果出现错误,我想更新该实体以显示错误状态(在服务类中)。
I've tried to achieve this by opening a separate transaction and committing the update:我试图通过打开一个单独的事务并提交更新来实现这一点:
@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();
}
}
}
However this approach freezes up the application entirely (or rather the batch job never finishes) on save
-> statelessSession.update(..)
for some reason!然而,由于某种原因,这种方法在
save
-> statelessSession.update(..)
时完全冻结了应用程序(或者更确切地说,批处理作业永远不会完成)! Note that the methods openStatelessSession
, save
, rollback
, commit
etc. are known to work in other contexts.请注意,已知方法
openStatelessSession
、 save
、 rollback
、 commit
等可在其他上下文中工作。 I copied them into this example.我将它们复制到这个例子中。 I also tried working with
repository
to load the entity but here of course I get StaleObjectException
.我也尝试使用
repository
来加载实体,但在这里我当然得到StaleObjectException
。
I also played around with @Transaction(Propagation.REQUIRES_NEW)
for a separate exception handler service but that persists all the changes in the entity manager and not only the ProcessEntry
entity.我还使用了
@Transaction(Propagation.REQUIRES_NEW)
来获得一个单独的异常处理程序服务,但这会保留实体管理器中的所有更改,而不仅仅是ProcessEntry
实体。
It is very likely since you freezing the database, not your app.这很可能是因为您冻结了数据库,而不是您的应用程序。 You are trying to insert a record that is already trying to be inserted in another transaction before the other transaction is completed.
您正在尝试插入一条记录,该记录在另一个事务完成之前已经尝试插入另一个事务。 Which is likley taking the database from a row lock to a full table lock.
这很可能将数据库从行锁变为全表锁。 So each transaction is waiting on the other to close before they can move forward.
因此,每笔交易都在等待对方关闭,然后才能继续前进。
You can try one of these:您可以尝试以下方法之一:
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.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.