Using Hibernate's OneToMany and JoinTable annotations to an Entity with a Composite Key with Quarkus

357 Views Asked by At

Hibernate's OneToMany/JoinTable with a Composite Key

I am having an issue with using the @JoinTable annotation when using a Composite Key in a One-To-Many relationship. I am using the Quarkus framework. The current error I am facing is 'Repeated column in mapping for collection'. This is because I am using duck_id in both joinColumns and inverseJoinColumns within the @JoinTable. Duck_id needs two be in both places.

Is there something different about using composite keys with Quarkus that I am missing? I have researched that Quarkus does support it, but I could not find any example using the JoinTable annotation.

It is important to note that I don't have a way to modify the database tables manually.

Tables

These two tables are in this scope.

  • duck
  • duck_mission

The second table 'duck_mission' has only two columns, 'duck_id' and 'mission_id' in which both columns are foreign keys to their corresponding tables. The two foreign keys together make up the primary key of table 'duck_mission'. This table uses a CompositeKey to satisfy the requirement that all Entities MUST have a primary key. Note: mission_id is a foreign key to another table not listed here called 'Mission'.

ENTITIES

DUCK ENTITY

@Entity
@Table(name = "duck")
public class DuckEntity extends PanacheEntityBase {

    @Id
    @GeneratedValue
    @Column(name = "id")
    public UUID id;

    @OneToMany
    @JoinTable(
            name = "duck_mission",
            joinColumns = @JoinColumn(name = "duck_id"),
            inverseJoinColumns = {
                    @JoinColumn(name = "duck_id", referencedColumnName = "duckId"),
                    @JoinColumn(name = "mission_id", referencedColumnName = "missionId")
            }
    )
    public List<DuckMissionEntity> duckMissions;

    //Other columns, getters, and setters

}

DUCK MISSION ENTITY

@Data
@Embeddable
class DuckMissionEntityId implements Serializable {

    @Column(name = "duck_id")
    public UUID duckId;

    @Column(name = "mission_id")
    public UUID missionId;
}

@Entity
@Table(name = "duck_mission")
public class DuckMissionEntity extends PanacheEntityBase {

    // The Composite Key
    @EmbeddedId
    public DuckMissionEntityId id;

    public UUID duckId;

    public UUID missionId;

    //Other Getters and Setters

}

I have tried using @IdClass instead of an embeddedId, but it seems there is no difference between them. I also have tried substituting other values in place of the joinColumns. If I add (nullable = false, insertable=false, updatable=false) to the inverseJoinColumns, I then get the error 'null id generated for:class'.

2

There are 2 best solutions below

1
Luca Basso Ricci On

In DuckMissionEntity you already mapped duckId and missionId via DuckMissionEntityId id so no needs to redeclare them as separate properties.

0
ShowAndTell On

I found a solution without using JoinTable which looks a lot cleaner in my opinion. Instead of one-directional, I have updated it to be bi-directional using OnetoMany and ManyToOne. It also uses mappedBy and mapsId to create a relationship from DuckMissionEntity to DuckEntity.

Note: @NoArgsConstructor and @AllArgsConstructor are likely not needed for this relationship to work but it is helpful to have when creating/modifying these entities in the business logic.

The updated entities:

@Entity
@Table(name = "duck", schema = "si")
public class DuckEntity extends PanacheEntityBase {

    @Id
    @GeneratedValue
    @Column(name = "id")
    public UUID id;

    @OneToMany(mappedBy = "duckEntity", cascade = CascadeType.ALL, orphanRemoval = true)
    public List<DuckMissionEntity> duckMissions;

    public void setDuckMissions(List<DuckMissionEntity> duckMissionEntities){
        this.duckMissions = duckMissionEntities;
    }

    @Transactional
    public static void save(DuckEntity duck) {
        try {
            duck.persist();
            if (!duck.isPersistent()) {
                LOGGER.error("DuckEntity {} was not persisted to DB", duck.id);
            }
        } catch(Exception e) {
            LOGGER.error("Could Not Save Duck", e);
        }
    }

}
@Data
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
class DuckMissionEntityId implements Serializable {

    @Column(name = "duck_id")
    public UUID duckId;

    @Column(name = "mission_id")
    public UUID missionId;

}

@Entity
@Table(name = "duck_mission", schema = "si")
@NoArgsConstructor
@AllArgsConstructor
public class DuckMissionEntity extends PanacheEntityBase {

    @EmbeddedId
    public DuckMissionEntityId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("duckId")
    @JoinColumn(name = "duck_id")
    public DuckEntity duckEntity;

    public DuckMissionEntity(UUID duckId, UUID missionId) {
        this.id = new DuckMissionEntityId(duckId, missionId);
    }

}