0

If i use this piece of code (CASE 1 in picture):

@Data
@Entity
public class PostOneUni {

    @EmbeddedId //if @Id, class CompositeId must be annotated @Embeddable?
    private CompositeId id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumns({
            @JoinColumn(name = "id1"),
            @JoinColumn(name = "id2")
    })
    private Set<PostCommentUniMany> comments = new HashSet<>();

    ...
}

and when execute creation of this object and add child comments, when saving to DB everything works as expected (ids on child PostCommentUniMany are added as expected) but changes are not propagated to java code (Object @12081 in picture should have id field updated to 2 and not null).

In other case (CASE 2) I use this code:

@Data
@Entity
public class PostOneUni {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "post_id")
    private Set<PostCommentUniMany> comments = new HashSet<>();

    ...
}

and as you can see from the picture, fields are also persisted to DB, and the object that was used to save state to DB is updated after save (Object @12052 is updated to reflect id field to be 2 - as in DB).

enter image description here

How to update object @12081 in CASE 1 to reflect DB id?

UPDATE

After discussion below - problem was that if id on entity is set manually to some value, Spring think it is not new entity and tries to do merging instead of persisting the entity.

One of "workarounds" is to set @Version field on @Entity class PostOneUni that will track if entity is new or not.

@Version
private Integer version;
Bojan Vukasovic
  • 2,054
  • 22
  • 43
  • You will need to post the code of the ID class and the code you use to create and persist these objects. – Alan Hay Jun 13 '19 at 10:32
  • @Alan Hay - I can. Composite is just standard lombok @_Value and saving is done with Spring Data JPA repos .save(PostOneUni) method using @_Transactional. – Bojan Vukasovic Jun 13 '19 at 11:04
  • You need to add the code not tell me about it. – Alan Hay Jun 13 '19 at 11:10
  • @AlanHay OK, I updated with code. – Bojan Vukasovic Jun 13 '19 at 11:50
  • The instance `comment2` and the instance `comment2` in the collection are different items in memory which is obviously not good. I would expect these to be the same instance in memory. How has this happened? – Alan Hay Jun 13 '19 at 12:47
  • @Alan Hay - this is because I load second collection using `findById` method on repository, and that one constructs completely new objects. This should be OK. It is just this first `comment2` that has `id=null` (which is ok state, but BEFORE saving), after calling `save` on repo - hibernate should update it to `id=2` (as is case in CASE 2). – Bojan Vukasovic Jun 13 '19 at 12:51
  • yeah, I know how it works but unfortunately cannot guess what you code is doing if you don't bother to post the relevant parts. – Alan Hay Jun 13 '19 at 12:53
  • @AlanHay Here is link to full project (this is root test case): https://github.com/bojanv55/hi-perf-java-persistence/blob/master/src/test/java/me/vukas/hiperfjavapersistence/service/OneToManyIT.java – Bojan Vukasovic Jun 13 '19 at 12:56
  • "that one constructs completely new objects". Assuming you have cleared 1st level cache. – Alan Hay Jun 13 '19 at 13:28
  • If it's not the same instance then it should be:https://stackoverflow.com/a/4226961/1356423 – Alan Hay Jun 13 '19 at 13:34
  • I found out that if I manually set `ID` on `Post` object (with `post.setId(1L);`), it behaves the same as `EmbeddedId` (does not update children (comment) ids). So it must be some logic in AUTO_INCREMENT key that is forcing propagation to children IDs. – Bojan Vukasovic Jun 13 '19 at 14:30

1 Answers1

1

The problem is that because you have set the ID fields manually Spring Data calls a merge operation rather than persist:

See org.springframework.data.jpa.repository.support.SimpleJpaRepository

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) //checks if ID null {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

And the JPA spec notes that when

3.2.7.1 Merging Detached Entity State

• If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.

In your test code if you therefore do:

post = oneToManyService.addNewPost(post);

the identifiers are set correctly on the returned instance just as they are when you reload from the database. However, the original instances (in 'new' state) remain unchanged i.e do not have IDs set.

Where persist is called rather than merge, then the original (same) instance is returned and the identifiers will be set correctly on this instance.

Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • Thanks for explanation. I will try this one. `JpaRepository` does not expose `persist`, so I have to check how to do this one. Isn't Spring Data smart enough to check if manually set Id is in DB and flag it as new? I also saw that there is `Version` field that can be set to make this decision. – Bojan Vukasovic Jun 13 '19 at 14:51
  • Spring Data is making the decision in the code posted. – Alan Hay Jun 13 '19 at 14:52
  • See here possibly. https://stackoverflow.com/a/37255257/1356423 Implement Persistable interface and define your own `isNew()` implementation. – Alan Hay Jun 13 '19 at 14:54
  • Progress :) Now I have `org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: me.vukas.hiperfjavapersistence.entity.relationship.onetomany.PostOneUni; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: me.vukas.hiperfjavapersistence.entity.relationship.onetomany.PostOneUni ` – Bojan Vukasovic Jun 13 '19 at 14:59
  • OK. I can confirm this was the problem. After adding field `@Version private Integer version;` to `PostOneUni`, it works as expected. Maybe there is some other way without using Versioning? – Bojan Vukasovic Jun 13 '19 at 15:12