繁体   English   中英

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

[英]Hibernate/Spring Boot: How to exclude an entity from rollback

描述

发生异常时,排除 Hibernate 托管实体在事务中回滚的最佳方法是什么?

我有一个由 Spring Batch 调用的服务。 Spring 批处理打开并提交我无法控制的事务。 在服务过程中,某些实体被更新。 但是,当发生异常时,它会传播给调用者,并且所有处理工作都会回滚。 这正是我想要的,除了一个特定的实体,它还用于跟踪进程 state 以便于监控和其他原因。 如果出现错误,我想更新该实体以显示错误状态(在服务类中)。

例子

我试图通过打开一个单独的事务并提交更新来实现这一点:

@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实体。

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

您可以尝试以下方法之一:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM