question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

spring-data-jpa: repository calls merge() when it should call persist() instead

See original GitHub issue

Describe the bug Although we do not work with detached entities, we ran into https://hibernate.atlassian.net/browse/HHH-9362 and/or https://hibernate.atlassian.net/browse/HHH-11414 for a rather simple persist scenario. In the call stack we can see that the generated repository is calling merge() instead of persist(). This is most likely due to this code in StockMethodsAdder:

// call EntityManager.persist of the entity does not have it's ID set, otherwise call EntityManager.merge

BytecodeCreator idValueUnset;
BytecodeCreator idValueSet;
if (idType instanceof PrimitiveType) {
    BranchResult idValueNonZeroBranch = save.ifNonZero(idValue);
    idValueSet = idValueNonZeroBranch.trueBranch();
    idValueUnset = idValueNonZeroBranch.falseBranch();
} else {
    BranchResult idValueNullBranch = save.ifNull(idValue);
    idValueSet = idValueNullBranch.falseBranch();
    idValueUnset = idValueNullBranch.trueBranch();
}

idValueUnset.invokeStaticMethod(
        MethodDescriptor.ofMethod(JpaOperations.class, "persist", void.class, Object.class),
        entity);
idValueUnset.returnValue(entity);

ResultHandle entityManager = idValueSet.invokeStaticMethod(
        ofMethod(JpaOperations.class, "getEntityManager", EntityManager.class));
entity = idValueSet.invokeInterfaceMethod(
        MethodDescriptor.ofMethod(EntityManager.class, "merge", Object.class, Object.class),
        entityManager, entity);
idValueSet.returnValue(entity);

We do pre-populate our IDs (custom strongly typed wrappers around UUID, e.g. UserId) so the generated code seems to think we have an update case of a detached entity, which is normally the only case when you really need merge(). JavaDoc of EnityManager.merge():

Merge the state of the given entity into the current persistence context.

Expected behavior Quarkus spring-data-jpa should only call merge() when really required, that is from my POV: merging a detached entity. With a pre-populated id, the initial persist should be performed with persist(), not merge().

Actual behavior Quarkus spring-data-jpa calls merge():

  • in a persist case when Id is pre-populated
  • in all update cases (Id is present)

To Reproduce Steps to reproduce the behavior:

  1. Will create a reproducer if required but I am pretty swamped right now.

Configuration n/a

Screenshots n/a

Environment (please complete the following information):

  • Output of uname -a or ver: MINGW64_NT-10.0-18363 XXX 3.0.7-338.x86_64 2019-11-21 23:07 UTC x86_64 Msys
  • Output of java -version:
    openjdk version "11.0.7" 2020-04-14
    OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
    OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)
    
  • GraalVM version (if different from Java): n/a
  • Quarkus version or git rev: 1.4.2.Final
  • Build tool (ie. output of mvnw --version or gradlew --version): Apache Maven 3.6.3

Additional context The original spring-data-jpa is more or less doing the same, but it is also checking the value of the @Version column: https://github.com/spring-projects/spring-data-jpa/blob/2.3.1.RELEASE/src/main/asciidoc/jpa.adoc#entity-state-detection-strategies See also:

On a side note: I don’t know why the docs say

Option 1 is not an option for entities that use manually assigned identifiers

because the code clearly shows that the Id-check is skipped when a version column is present.

This would help in our case for the inital persist, but it would still use merge() for updates which means we might run into HHH-9362 and it would be “incorrect” since our entities are already attached when they are updated.

Alternatively, org.springframework.data.domain.Persistable.isNew() would also help, but again only for the intial persist. The interface is present in Quarkus spring-data-japa, but the method is not called.

What I am proposing here for the update case actually goes futher than what Spring is doing which might justify a configuration property to control that behaviour: Only use merge() if EntityManager.contains() returns false.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:32 (31 by maintainers)

github_iconTop GitHub Comments

2reactions
famodcommented, Jun 22, 2020

Then we should turn it around: Call merge() unless contains() returns true.

I’m still confused 😃 That’s going to break both merge and persist use cases:

It’s a typical monday for me so I am probably confused too. 😉

Later today I’ll try to write down the rules I am thinking of. So maybe I’ll come to the same conclusion eventually.

1reaction
geoandcommented, Jun 24, 2020

Actually, I’ll take a look at adding support for @Version as this seems to be used quite widely and as already mentioned it is what regular spring-data-jpa does.

But I don’t think I’ll be doing anything more for this ticket unless we find a really good reason to

Read more comments on GitHub >

github_iconTop Results From Across the Web

JpaRepository merge() method - spring boot - Stack Overflow
I think that with merge call, if inside the database exists a SubEntity with specified id and other fields valorized, the other fields...
Read more >
Hibernate: save, persist, update, merge, saveOrUpdate
A quick and practical guide to Hibernate write methods: save, persist, update, merge, saveOrUpdate.
Read more >
How do persist and merge work in JPA - Vlad Mihalcea
In this article, I'm going to explain how the persist and merge entity operations work when using JPA and Hibernate.
Read more >
Hibernate's persist(), save(), merge() and update()
JPA's merge method copies the state of a detached entity to a managed instance of the same entity. Hibernate, therefore, executes an SQL...
Read more >
2. JPA Repositories - Spring
save(…) -Method. It will persist or merge the given entity using the underlying JPA EntityManager . If the entity has not been persisted...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found