Tyvärr tror jag att den lilla skillnaden att du bara har ett bord är problemet här.
Titta på deklarationen för PhoneId
klass (som jag skulle föreslå är bättre kallad PhoneOwner
eller något liknande):
@Entity
@Table(name="Phones")
public class PhoneId {
När du deklarerar att en klass är en entitet som är mappad till en viss tabell, gör du en uppsättning påståenden, varav två är särskilt viktiga här. För det första att det finns en rad i tabellen för varje instans av entiteten, och vice versa. För det andra att det finns en kolumn i tabellen för varje skalärt fält i entiteten, och vice versa. Båda dessa är kärnan i idén om objektrelationell kartläggning.
Men i ditt schema håller inget av dessa påståenden. I uppgifterna du angav:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Det finns två rader som motsvarar entiteten med owner_id
1, strider mot det första påståendet. Det finns kolumner TYPE
och NUMBER
som inte är mappade till fält i entiteten, vilket bryter mot det andra påståendet.
(För att vara tydlig är det inget fel med din deklaration av Phone
klass eller phones
fältet - bara PhoneId
enhet)
Som ett resultat, när din JPA-leverantör försöker infoga en instans av PhoneId
in i databasen får den problem. Eftersom det inte finns några mappningar för TYPE
och NUMBER
kolumner i PhoneId
, när den genererar SQL för infogningen, inkluderar den inte värden för dem. Det är därför du får felmeddelandet du ser - leverantören skriver INSERT INTO Phones (owner_id) VALUES (?)
, som PostgreSQL behandlar som INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, vilket avvisas.
Även om du lyckades infoga en rad i den här tabellen, skulle du få problem med att hämta ett objekt från den. Säg att du bad om instansen PhoneId
med owner_id
1. Leverantören skulle skriva SQL motsvarande select * from Phones where owner_id = 1
, och den skulle förvänta sig att hitta exakt en rad, som den kan mappa till ett objekt. Men den kommer att hitta två rader!
Lösningen är, är jag rädd, att använda två tabeller, en för PhoneId
, och en för Phone
. Tabellen för PhoneId
kommer att vara trivialt enkelt, men det är nödvändigt för att JPA-maskineriet ska fungera korrekt.
Förutsatt att du byter namn på PhoneId
till PhoneOwner
, tabellerna måste se ut så här:
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)
)
(Jag har gjort (owner_id, number)
den primära nyckeln för Phone
, under antagandet att en ägare kan ha mer än ett nummer av en given typ, men kommer aldrig att ha ett nummer registrerat under två typer. Du kanske föredrar (owner_id, type)
om det bättre återspeglar din domän.)
Entiteterna är då:
@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;
}
Nu, om du verkligen inte vill introducera en tabell för PhoneOwner
, då kanske du kan ta dig ur det med en vy. Så här:
create view PhoneOwner as select distinct owner_id from Phone;
Såvitt JPA-leverantören kan se är detta en tabell och den kommer att stödja de frågor den behöver göra för att läsa data.
Det kommer dock inte att stödja inlägg. Om du någonsin behövt lägga till en telefon för en ägare som för närvarande inte finns i databasen, måste du gå runt baksidan och infoga en rad direkt i Phone
. Inte särskilt trevligt.