A composite key uses more than one column to identify a row. Each column may repeat, but the combination must be unique. In an IMAGE table, (ITEM_ID, FILENAME) identifies an image attached to an item: the same filename cannot be attached twice to that item. section 8.1.2 section 8.1.4
The key follows the relationship being stored:
- For a Set of image filenames, ITEM_ID and FILENAME form the collection table’s primary key. The table stores no image order. section 8.1.4
- For a Map keyed by an embeddable Filename, ITEM_ID and NAME form the primary key. NAME cannot be null; Filename also needs value-based equals() and hashCode() for Java map-key uniqueness. section 8.2.5
- For a many-to-many association, CATEGORY_ID and ITEM_ID identify a link. The pair prevents duplicate links while allowing either entity to participate in other links. section 9.3.1
A link with its own identifier
When a link must also record who created it and when, map the link table as an entity. Its embeddable identifier holds categoryId and itemId; an @EmbeddedId maps that pair to CATEGORY_ID and ITEM_ID. Unlike a collection element, each link entity is saved or deleted independently, though cascading persistence can reduce save calls. section 9.3.2
Listing 9.4 Mapping a many-to-many relationship with CategorizedItem
Path: manytomany-linkentity/src/main/java/com/manning/javapersistence /ch09/manytomany/linkentity/CategorizedItem.java @Entity @Table(name = "CATEGORY_ITEM") @org.hibernate.annotations.Immutable #1 public class CategorizedItem { @Embeddable public static class Id implements Serializable { #2 @Column(name = "CATEGORY_ID") private Long categoryId; @Column(name = "ITEM_ID") private Long itemId; public Id() { } public Id(Long categoryId, Long itemId) { this.categoryId = categoryId; this.itemId = itemId; } //implementing equals and hashCode } @EmbeddedId #3 private Id id = new Id(); @Column(updatable = false) @NotNull private String addedBy; #4 @Column(updatable = false) @NotNull @CreationTimestamp private LocalDateTime addedOn; #5 @ManyToOne @JoinColumn( name = "CATEGORY_ID", insertable = false, updatable = false) private Category category; #6 @ManyToOne @JoinColumn( name = "ITEM_ID", insertable = false, updatable = false) private Item item; #7 public CategorizedItem( String addedByUsername, #8 Category category, Item item) { this.addedBy = addedByUsername; #9 this.category = category; this.item = item; this.id.categoryId = category.getId(); #10 this.id.itemId = item.getId(); category.addCategorizedItem(this); #9 item.addCategorizedItem(this); } // . . . } #1 The class is immutable, as annotated with <kbd>@org.hibernate.annotations .Immutable</kbd>. #2 An entity class needs an identifier property. The primary key of the link table is the composite of <kbd>CATEGORY_ID</kbd> and <kbd>ITEM_ID</kbd>. We can externalize this <kbd>Id</kbd> class into its own file, of course. #3 The new <kbd>@EmbeddedId</kbd> annotation maps the identifier property and its composite key columns to the entity’s table. #4 The basic property mapping the <kbd>addedBy</kbd> username to a column of the join table. #5 The basic property mapping the <kbd>addedOn</kbd> timestamp to a column of the join table. This is the “additional information about the link” that interests us. #6 The <kbd>@ManyToOne</kbd> property <kbd>category</kbd> is already mapped in the identifier. #7 The <kbd>@ManyToOne</kbd> property <kbd>item</kbd> is already mapped in the identifier. The trick here is to make them read-only, with the <kbd>updatable=false</kbd>, <kbd>insertable=false</kbd> settings. This means Hibernate or Spring Data JPA using Hibernate writes the values of these columns by taking the identifier value of <kbd>CategorizedItem</kbd>. At the same time, we can read and browse the associated instances through <kbd>categorizedItem .getItem()</kbd> and <kbd>getCategory()</kbd>. (If we map the same column twice without making one mapping read-only, Hibernate or Spring Data JPA using Hibernate will complain on startup about a duplicate column mapping.) #8 We can also see that constructing a <kbd>CategorizedItem</kbd> involves setting the values of the identifier. The application always assigns composite key values; Hibernate doesn’t generate them. #9 The constructor sets the <kbd>addedBy</kbd> field value and guarantees referential integrity by managing collections on both sides of the association. #10 The constructor sets the <kbd>categoryId</kbd> field value. We’ll map these collections next to enable bidirectional navigation. This is a unidirectional mapping and enough to support the many-to-many relationship between <kbd>Category</kbd> and <kbd>Item</kbd>. To create a link, we instantiate and persist a <kbd>CategorizedItem</kbd>. If we want to break a link, we remove the <kbd>CategorizedItem</kbd>. The constructor of <kbd>CategorizedItem</kbd> requires that we provide already persistent <kbd>Category</kbd> and <kbd>Item</kbd> instances.
The mapping shows the two identifier fields inside an @Embeddable Id class and the entity’s @EmbeddedId property. section 9.3.2
Check nullability when generating schemas. Hibernate includes an embeddable collection element’s non-nullable columns in its composite primary key. Use @Column(nullable=false) for a property that must join that key; @NotNull alone may validate the object without generating the required database constraint. For a ternary link, this distinction can keep USER_ID and ADDEDON outside the intended (CATEGORY_ID, ITEM_ID) key, but their missing SQL NOT NULL constraints must be fixed manually. An embeddable link has a simpler collection-managed lifecycle, at the cost of bidirectional navigation. section 8.2.1 section 9.3.3







