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.