Add @ManyToOne as part of the composite primary key in Hibernate JPA

This time Pain (≒ diary)

=> Yes, let's use a compound primary key

Table example

For MySQL, for example, such a table

CREATE TABLE parent(
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  bar VARCHAR(30) NOT NULL
);

CREATE TABLE parent_child(
  parent_id INT NOT NULL,
  value_ VARCHAR(30) NOT NULL,

  PRIMARY KEY(parent_id, value_),
  FOREIGN KEY(parent_id) REFERENCES parent(id)
);

ChildEntity Since it is a composite primary key, we use @EmbeddedId, but the reference to the parent entity with @ ManyToOne should be in the field of the __ outer Entity class instead of the PK class __. [^ 1] [^ 1]: StackOverflowError occurs during read operation when the PK class has a field for @ ManyToOne.

By adding @MapsId together with @ManyToOne, the ID on the parent table side will be assigned when the parent and child are inserted at the same time. However, in the case of parent update child insert, it doesn't take care of it, so you have to synchronize parent and parentId yourself.

ChildEntity.java


@Entity
@Table(name = "parent_child")
@ToString(exclude = "parent")
@NoArgsConstructor
public class ChildEntity {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class PK implements Serializable {
        @Column(name = "parent_id")
        private int parentId;

        @Column(name = "value_")
        private String value;
    }

    @EmbeddedId
    @Getter
    @Setter
    private PK pk;

    @ManyToOne
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    @MapsId("parentId")
    @Getter
    @Setter
    private ParentEntity parent;

    public ChildEntity(@NonNull final ParentEntity parent,
            @NonNull final String value) {
        this.pk = new PK(parent.getId(), value);
        this.parent = parent;
    }

    public void setParent(@NonNull final ParentEntity parent) {
        this.pk.setParentId(parent.getId());
        this.parent = parent;
    }
}

Note that careless use of lombok.ToString, lombok.EqualsAndHashCode, Jackson, etc. will cause an infinite loop because it is a circular reference between parent and child.

ParentEntity The parent side is no different from the normal @OneToMany (when using surrogate keys).

ParentEntity.java


@Entity
@Table(name = "parent")
@Data
public class ParentEntity {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToMany(mappedBy = "parent", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @Fetch(FetchMode.SUBSELECT)
    private List<ChildEntity> children;

    @Column(name = "bar")
    private String bar;
}

Note that if you do not set orphanRemoval in the parameter of @ OneToMany, the child record will not be deleted even if you delete the child on the parent side. Also, if you do not add @ Fetch (FetchMode.SUBSELECT), the N + 1 problem will occur.

SQL issued at the time of update

Hibernate 5.0.12 looks like this. When inserting the newly added ChildEntity, I am concerned about the fact that each item is selected separately from the List acquired together with ParentEntity. For the elements that haven't changed, EAGER fetch is enough, so I wonder if there is any problem in this project.

select parententi0_.id as id1_0_0_, parententi0_.bar as bar2_0_0_, children1_.parent_id as parent_i1_1_1_, children1_.value_ as value_2_1_1_, children1_.parent_id as parent_i1_1_2_, children1_.value_ as value_2_1_2_ from parent parententi0_ left outer join parent_child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
binding parameter [1] as [INTEGER] - [5]
select childentit0_.parent_id as parent_i1_1_0_, childentit0_.value_ as value_2_1_0_ from parent_child childentit0_ where childentit0_.parent_id=? and childentit0_.value_=?
binding parameter [1] as [INTEGER] - [5]
binding parameter [2] as [VARCHAR] - [0.42733237750132513]
insert into parent_child (parent_id, value_) values (?, ?)
binding parameter [1] as [INTEGER] - [5]
binding parameter [2] as [VARCHAR] - [0.42733237750132513]
delete from parent_child where parent_id=? and value_=?
binding parameter [1] as [INTEGER] - [5]
binding parameter [2] as [VARCHAR] - [0.8694858141499555]

Recommended Posts

Add @ManyToOne as part of the composite primary key in Hibernate JPA
hibernate composite key
Until the use of Spring Data and JPA Part 2
Until the use of Spring Data and JPA Part 1
Generate a serial number with Hibernate (JPA) TableGenerator and store it in the Id of String.
Summarize the main points of getting started with JPA learned with Hibernate
Defeat the hassle of treating C arrays as Tuples in Swift
How to get the id of PRIMAY KEY auto_incremented in MyBatis