Search code examples
javahibernatejpaormnhibernate-mapping

Unidirectional @OneToMany does not work as expected with @EmbeddedId


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;

Solution

  • 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.