简体   繁体   English

在不同的线程上运行时,Hibernate不必要地创建新实体

[英]Hibernate Creates new Entity Unnecessarily When Running on a Different Thread

I am using Spring Data JPA, running the following test results in counter-intuitive behavior 我正在使用Spring Data JPA,以违反直觉的方式运行以下测试结果

@Test
public void testAsync() throws ExecutionException, InterruptedException {
    Job job = jobRepository.save(new Job());
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    long origJobID = job.getId();
    executor.initialize();
    Future<?> wait = executor.submit(() -> {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Job outcome = jobRepository.save(job.setStopTime(Instant.now()));
        // this assertion fails, Hibernate requested a new ID and persisted a new entity ... even though I am reusing the same instance with an ID already populated
        assertEquals(origJobID, outcome.getId().longValue());
    });
    wait.get();
}

Since jobRepository only provide a save() interface, as a user I of the service I can only call this method to INSERT or UPDATE my entity ... how is it possible that the underlying entity manager just ignore the fact that my entity already has an ID and goes ahead and creates a duplicate row? 由于jobRepository仅提供一个save()接口,作为服务的用户,我只能调用此方法来插入或更新我的实体……基础实体管理器怎么可能忽略我的实体已经具有的事实一个ID并继续创建重复的行?

Looking further into Hibernate's codebase, it appears on a new Thread, the persistentContext is wiped clean. 进一步研究Hibernate的代码库,它出现在新的Thread上,擦除了persistentContext。 Therefore my entity is turned into a DETACHED state as far as the DefaultMergeEventListener is concerned ... creating a cascade of decisions that inexplicably leading to the generation of a new ID 因此,就DefaultMergeEventListener而言,我的实体已变成DETACHED状态...创建了一系列决定,这些决定莫名其妙地导致生成新ID

The specific code I reference is located at: https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L109 我引用的特定代码位于: https : //github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L109

If run from a new Thread, Hibernate's persistence context is empty ... which is fine .... but then I don't understand why is my entity now considered DETACHED ... 如果从新线程运行,则Hibernate的持久性上下文为空...这很好....但是我不明白为什么现在将我的实体视为DETACHED ...

More excerpt from Hibernate's source code: Default https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L293 摘自Hibernate的源代码:默认https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L293

if ( result == null ) {
            //TODO: we should throw an exception if we really *know* for sure
            //      that this is a detached instance, rather than just assuming
            //throw new StaleObjectStateException(entityName, id);

            // we got here because we assumed that an instance
            // with an assigned id was detached, when it was
            // really persistent
            entityIsTransient( event, copyCache );
        }

EDIT If this is related to the fact that the transactions are not committed yet, and the new thread does not use the same transaction ... is there a way to force a transaction in code? 编辑如果这与尚未提交事务有关,并且新线程不使用同一事务,那么是否有办法在代码中强制执行事务?

Note calling JpaRepository.saveAndFlush() does not resolve the issue 注意调用JpaRepository.saveAndFlush()不能解决问题

EDIT 2 I am using embedded h2 for this test, in either case, I expected saveAndFlush() to have committed the transaction to the database (embedded or on the other side of the world) so that multiple threads can use the JpaRepository to view the states saved by the other Threads right? 编辑2我正在为此测试使用嵌入式h2,无论哪种情况,我都希望saveAndFlush()已将事务提交到数据库(嵌入式或在世界的另一端),以便多个线程可以使用JpaRepository查看其他线程保存的状态正确吗?

EDIT 3 Looking at other similar questions, it appears marking the test method itself as @Transaction(propagation = NOT_SUPPORTED) forces the underlying transaction manager to commit upon JpaRepository.save() ... this is still confusing ... how was the test method transactional to begin with? 编辑3看着其他类似的问题,似乎将测试方法本身标记为@Transaction(propagation = NOT_SUPPORTED)强制底层事务管理器对JpaRepository.save()进行提交……这仍然令人困惑……测试如何进行事务性方法从什么开始? ie why did the transaction not commit to begin with? 即为什么交易没有承诺开始?

A very rough idea of how it works: 关于其工作原理的一个非常粗略的想法:

  • You have multiple threads 您有多个线程
  • Transactions are typically thread-bound (each thread gets its own transaction) 事务通常是线程绑定的(每个线程都有自己的事务)
  • EntityManager s are typically transaction-bound (each transaction has its own EntityManager , which sees things according to that transaction) EntityManager通常是受事务绑定的(每个事务都有自己的EntityManager ,它根据该事务查看事物)

What you are doing is 你在做什么

  1. Tell the entity manager in thread 1 that you want to make entity A persistent / save its changes (that is what save() means in Spring Data) 告诉线程1中的实体管理器您要使实体A持久化/保存其更改(这就是Spring Data中的save()的含义)
  2. Pass entity A to thread 2 将实体A传递给线程2
  3. Tell the entity manager in thread 2 that you want to make entity A persistent / save its changes 告诉线程2中的实体管理器您要使实体A持久化/保存其更改

As the entity managers are in different transactions and thread 1 has not committed its transaction when 3. is executed, from the point of view of thread 2 entity A is not yet persistent; 由于实体管理器处于不同的事务中,并且在执行线程3.时线程1尚未提交其事务,因此从线程2的角度来看,实体A尚未持久; so it will interpret save() (or saveAndFlush() ) as "make entity A persistent" and not as "save changes in entity A". 因此它将将save() (或saveAndFlush() )解释为“使实体A成为持久性”,而不是“保存实体A中的更改”。 And making entity A persistent implies assigning a new ID to entity A if necessary. 而使实体A持久化则意味着在必要时为实体A分配新的ID。

You should be able to make this work by ensuring that thread 1 has already committed when step 3. is executed. 您应该能够通过在执行步骤3时确保已经提交了线程1来完成这项工作。 Anyway I do not recommend passing managed entity references between threads as it gets tricky really fast. 无论如何,我不建议在线程之间传递托管实体引用,因为它变得非常棘手。

About Spring Data transaction demarcation, you can read https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions for more information, but it basically involves making each transaction a method and then annotating that method. 关于Spring Data事务划分,您可以阅读https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions以获取更多信息,但它基本上涉及使每个事务成为一种方法,然后注释该方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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