Search code examples
hibernatespring-data-jpahibernate-mapping

How to create a OneToMany relationship targeting a combined primary key in Hibernate?


I'm working on a deck building card game using Spring Data and Hibernate. I would like to map the following PostGreSQL tables:

CREATE TABLE Deck(
    ID UUID NOT NULL,
    name VARCHAR(255) NOT NULL,
    PRIMARY KEY(ID)
);

CREATE TABLE DeckEntry(
    deckID UUID NOT NULL,
    cardID UUID NOT NULL,
    cardCount INT NOT NULL,

    PRIMARY KEY(deckID, cardID),
    CONSTRAINT fk_deckEntry_deck FOREIGN KEY(deckID) REFERENCES Deck(ID) ON DELETE CASCADE
)

The idea here is that:

  • a card can appear cardCount times in a deck.
  • a card can appear in multiple decks.
  • the DeckEntry represents the relationship and contains the count, but has no primary key of its own.

Here's what I've got in terms of mappings:

@Entity
public class DeckEntry {
    
    @EmbeddedId
    private DeckEntryID id;

    @Column
    private int cardCount;

    // hashCode and equals based on id
}

@Embeddable
public class DeckEntryID {
   
    @Column
    private UUID deckID;

    @Column
    private UUID cardID;

    // hashcode & equals based on deckId and cardID
}
@Entity
public class Deck {

   @Id
   private UUID id;

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

   @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
   private Set<DeckEntry> entries;

   // hashCode and equals based on id
}

My question is: What else do I need in terms of mapping annotations on Deck.entries to make this work as intended? The main issue here is that the target (DeckEntry) has a combined primary key, and one of the parts that make up this primary key is the Deck ID itself. This is crucial information for building the SQL statements, but I don't know how to convey this information to hibernate.


Solution

  • Through some experimentation, I've found the following solution which seems to do exactly what I need:

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.deckID")
    private Set<DeckEntry> entries;
    

    Note the mappedBy - I didn't know that it's possible to "step into" an embedded ID by using the dot syntax. I've checked the SQL queries generated by this mapping and they seem to be valid.