PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

JPA-Zuordnung mehrerer Zeilen mit ElementCollection

Leider denke ich, dass der kleine Unterschied, dass Sie nur einen Tisch behalten, hier das Problem ist.

Sehen Sie sich die Deklaration der PhoneId an Klasse (was ich vorschlagen würde, heißt besser PhoneOwner oder so ähnlich):

@Entity
@Table(name="Phones")
public class PhoneId {

Wenn Sie deklarieren, dass eine Klasse eine Entität ist, die einer bestimmten Tabelle zugeordnet ist, machen Sie eine Reihe von Behauptungen, von denen zwei hier besonders wichtig sind. Erstens gibt es eine Zeile in der Tabelle für jede Instanz der Entität und umgekehrt. Zweitens gibt es in der Tabelle für jedes Skalarfeld der Entität eine Spalte und umgekehrt. Beides steht im Mittelpunkt der Idee des objektrelationalen Mappings.

In Ihrem Schema trifft jedoch keine dieser Behauptungen zu. In den von Ihnen angegebenen Daten:

OWNER_ID    TYPE      NUMBER
  1         home      792-0001
  1         work      494-1234
  2         work      892-0005

Es gibt zwei Zeilen, die der Entität mit owner_id entsprechen 1, Verletzung der ersten Behauptung. Es gibt Spalten TYPE und NUMBER die keinen Feldern in der Entität zugeordnet sind, wodurch die zweite Behauptung verletzt wird.

(Um es klarzustellen, an Ihrer Deklaration des Phone ist nichts falsch Klasse oder die phones Feld - nur die PhoneId Entität)

Wenn Ihr JPA-Anbieter daher versucht, eine Instanz von PhoneId einzufügen in die Datenbank, läuft es in Schwierigkeiten. Weil es keine Zuordnungen für den TYPE gibt und NUMBER Spalten in PhoneId , wenn es die SQL für die Einfügung generiert, enthält es keine Werte für sie. Aus diesem Grund erhalten Sie den Fehler, den Sie sehen – der Anbieter schreibt INSERT INTO Phones (owner_id) VALUES (?) , die PostgreSQL als INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null) behandelt , die abgelehnt wird.

Selbst wenn Sie es geschafft hätten, eine Zeile in diese Tabelle einzufügen, würden Sie beim Abrufen eines Objekts aus ihr Probleme bekommen. Angenommen, Sie haben nach der Instanz von PhoneId gefragt mit owner_id 1. Der Anbieter würde SQL in Höhe von select * from Phones where owner_id = 1 schreiben , und es würde erwarten, dass genau eine Zeile gefunden wird, die es einem Objekt zuordnen kann. Aber es werden zwei Zeilen gefunden!

Die Lösung besteht leider darin, zwei Tabellen zu verwenden, eine für PhoneId und eine für Phone . Die Tabelle für PhoneId wird trivial einfach sein, aber es ist notwendig für den korrekten Betrieb der JPA-Maschinerie.

Angenommen, Sie benennen PhoneId um an PhoneOwner , müssen die Tabellen wie folgt aussehen:

create table PhoneOwner (
    owner_id integer primary key
)

create table Phone (
    owner_id integer not null references PhoneOwner,
    type varchar(255) not null,
    number varchar(255) not null,
    primary key (owner_id, number)
)

(Ich habe (owner_id, number) gemacht der Primärschlüssel für Phone , unter der Annahme, dass ein Eigentümer möglicherweise mehr als eine Nummer eines bestimmten Typs hat, aber niemals eine Nummer unter zwei Typen gespeichert hat. Vielleicht bevorzugen Sie (owner_id, type) wenn das besser zu Ihrer Domain passt.)

Die Entitäten sind dann:

@Entity
@Table(name="PhoneOwner")
public class PhoneOwner {
    @Id
    @Column(name="owner_id")
    long id;

    @ElementCollection
    @CollectionTable(name = "Phone", joinColumns = @JoinColumn(name = "owner_id"))
    List<Phone> phones = new ArrayList<Phone>();
}

@Embeddable
class Phone {
    @Column(name="type", nullable = false)
    String type;
    @Column(name="number", nullable = false)
    String number;
}

Wenn Sie jetzt wirklich keine Tabelle für den PhoneOwner einführen wollen , dann können Sie möglicherweise mit einer Ansicht herauskommen. So:

create view PhoneOwner as select distinct owner_id from Phone;

Soweit der JPA-Anbieter das beurteilen kann, handelt es sich hierbei um eine Tabelle, die die Abfragen unterstützt, die zum Lesen von Daten erforderlich sind.

Es werden jedoch keine Einfügungen unterstützt. Wenn Sie jemals ein Telefon für einen Eigentümer hinzufügen mussten, der sich derzeit nicht in der Datenbank befindet, müssten Sie um die Rückseite gehen und eine Zeile direkt in Phone einfügen . Nicht sehr schön.