简体   繁体   English

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

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

Description描述

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).如果出现错误,我想更新该实体以显示错误状态(在服务类中)。

Example例子

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.请注意,已知方法openStatelessSessionsaverollbackcommit等可在其他上下文中工作。 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.

相关问题 在 Spring Boot 中排除 Hibernate 不起作用 - Exclude Hibernate in Spring Boot not working 如何防止 Spring Boot/Hibernate 将实体列名称从 PascalCase 转换为 snake_case? - How to prevent Spring Boot/Hibernate from converting entity column names from PascalCase to snake_case? 使用Spring Boot和Hibernate保护单个实体免于级联删除 - Protect a single entity from cascade deletion with Spring Boot and Hibernate 如何将存储过程的结果 map 存储到 spring-boot/hibernate 中的实体 - How to map the results of a stored procedure to an entity in spring-boot/hibernate 休眠搜索。 如何从索引中排除实体? - Hibernate search. How to exclude entity from index? 如何在 spring 引导中从前端查找实体? - How to find entity from frontend in spring boot? 如何在 Spring Boot 上以相同的方法回滚 2 个事务 - How to rollback with 2 transaction in same method on Spring Boot Spring Boot中的Hibernate JPA / CrudRepository实体锁定 - Hibernate JPA/CrudRepository Entity Locking in Spring Boot Spring Boot:Hibernate无法将实体映射到Postgresql中的表:Spring Boot - spring boot: hibernate cannot mapping entity to table in postgresql: spring boot @Transactional 在 spring boot 中不回滚 - @Transactional is not rollback in spring boot
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM