Zusammengesetzter Schlüssel bei 1-zu-N Beziehungen (JPA)
Stößt man bei JPA auf eine bestehende Datenbank für die Entity Mappings erstellt werden sollen, so stößt man mitunter auf Tabellen die sich nicht ohne weiteres umsetzten lassen. Im Moment erstellen ein Kommiliton und ich eine Persistenzschicht für die Sakila DB, eine Beispieldatenbank für MySQL.
Das nebenstehende Bild zeigt eine Übersicht über die Tabellen, die wir ohne Änderungen als JPA Entities umsetzen. Dabei stellt sich die Umsetzung des Rental als besonders harkelig heraus. Dies ergibt sich aus der Wahl des Primärschlüssels, der auf 2 Attributen zusammengesetzt wird, dem “rent_date” und der “inventory_id”. Ein Inventargegenstand kann somit nur einmal zur gleichen Zeit ausgeliehen werden.
CREATE TABLE rental (
rent_date timestamp(0) without time zone NOT NULL,
inventory_id integer NOT NULL,
customer_id integer NOT NULL,
return_date timestamp(0) without time zone,
staff_id integer NOT NULL
);
CREATE TABLE inventory (
inventory_id integer NOT NULL,
film_id integer NOT NULL,
store_id integer NOT NULL
);
Zur Realisierung des zusammengesetzten Schlüssels haben wir uns für den Einsatz einer @IdClass entschieden. Richtig interessant wird es bei der Umsetzung dieser Klasse, denn ein Rental-Objekt soll eine @ManyToOne Beziehung zu einem Inventory haben und dies in der Objektwelt direkt referenzieren. Gleichzeitig wird die Id dieses Inventory aber auch als ein identifizierendes Attribut verwendet. Das Inventory-Objekt, das aus der @ManyToOne Beziehung resultiert, kann dazu nicht verwendet werden, denn die zulässigen Typen für Schlüssen sind in der JPA Spezifikation wie folgt definiert:
The primary key (or field or property of a composite primary key) should be one of the following types:
any Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date;
java.sql.Date. (JPA Spec 2.1.4)
Zur Lösung des Problems werden dem Rental zwei Attribute hinzugefügt. Bei dem Ersten handelt es sich um das referenzierte Inventory-Objekt, das mittels @ManyToOne Annotation versehen wird. Das Zweite ist die id dieses Inventory, diesmal aber als integer. In der Primary-Key-Klasse (RentalPK) verwendet nun das Ausleihdatum und die Id des Inventory.
@Entity
@IdClass(RentalPk.class)
public class Rental {
@Id
@Temporal(TemporalType.DATE)
@Column(name="rent_date", nullable=false)
private Date rentDate;
@ManyToOne
@JoinColumn(name="inventory_id", nullable=false)
private Inventory inventory;
@Id
@Column(name="inventory_id", nullable=false, updatable=false, insertable=false)
private int inventoryId;
..
}
public class RentalPk {
private Date rentDate;
private int inventoryId;
..
}
Wie in Zeile 11 und 15 zu sehen ist, wird die Spalte inventory_id von Rental mehrfach verwendet. Dabei ist besonders auf die Attribute der Annotationen zu achten. updatable=false und insertable=false bewirken, dass das Attribut inventoryId nicht in UPDATE oder INSERT SQL Statements aufgenommen werden. Statt dessen wird die entsprechende Spalte nur dann geändert, wenn die Objekt-Referenz des Inventory verändert wird.