Niestety, myślę, że problemem jest niewielka różnica polegająca na tym, że trzymasz tylko jeden stół.
Spójrz na deklarację PhoneId
klasa (która sugerowałbym lepiej nazywać się PhoneOwner
lub coś takiego):
@Entity
@Table(name="Phones")
public class PhoneId {
Kiedy deklarujesz, że klasa jest jednostką odwzorowaną na określoną tabelę, tworzysz zestaw asercji, z których dwa są tutaj szczególnie ważne. Po pierwsze, że w tabeli jest jeden wiersz dla każdej instancji jednostki i na odwrót. Po drugie, że w tabeli jest jedna kolumna dla każdego pola skalarnego encji i na odwrót. Oba te elementy leżą u podstaw idei mapowania obiektowo-relacyjnego.
Jednak w twoim schemacie żadne z tych twierdzeń nie ma racji bytu. W danych, które podałeś:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Istnieją dwa wiersze odpowiadające encji o owner_id
1, z naruszeniem pierwszego twierdzenia. Istnieją kolumny TYPE
i NUMBER
które nie są mapowane na pola w encji, naruszając drugie stwierdzenie.
(Aby było jasne, nie ma nic złego w deklaracji Phone
klasa lub phones
pole - tylko PhoneId
podmiot)
W rezultacie, gdy dostawca JPA próbuje wstawić instancję PhoneId
do bazy danych, wpada w kłopoty. Ponieważ nie ma mapowań dla TYPE
i NUMBER
kolumny w PhoneId
, gdy generuje kod SQL dla wstawienia, nie zawiera dla nich wartości. Dlatego pojawia się błąd, który widzisz — dostawca zapisuje INSERT INTO Phones (owner_id) VALUES (?)
, które PostgreSQL traktuje jako INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, który został odrzucony.
Nawet jeśli zdołasz wstawić wiersz do tej tabeli, będziesz miał kłopoty z odzyskaniem z niej obiektu. Załóżmy, że prosiłeś o wystąpienie PhoneId
z owner_id
1. Dostawca napisałby SQL o wartości select * from Phones where owner_id = 1
, i oczekuje, że znajdzie dokładnie jeden wiersz, który może zamapować na obiekt. Ale znajdzie dwa rzędy!
Obawiam się, że rozwiązaniem jest użycie dwóch tabel, jednej dla PhoneId
, a jeden dla Phone
. Tabela dla PhoneId
będzie banalnie prosty, ale jest niezbędny do prawidłowego działania maszyn JPA.
Zakładając, że zmienisz nazwę PhoneId
do PhoneOwner
, tabele muszą wyglądać tak:
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)
)
(Zrobiłem (owner_id, number)
klucz podstawowy dla Phone
, przy założeniu, że jeden właściciel może mieć więcej niż jedną liczbę danego typu, ale nigdy nie będzie miał jednej liczby zapisanej w dwóch rodzajach. Możesz preferować (owner_id, type)
jeśli to lepiej odzwierciedla Twoją domenę).
Podmioty są wtedy:
@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;
}
Teraz, jeśli naprawdę nie chcesz wprowadzać tabeli dla PhoneOwner
, możesz się z niego wydostać za pomocą widoku. Tak:
create view PhoneOwner as select distinct owner_id from Phone;
O ile dostawca JPA może powiedzieć, jest to tabela i będzie obsługiwać zapytania, które musi wykonać, aby odczytać dane.
Jednak nie obsługuje wstawek. Jeśli kiedykolwiek musiałeś dodać telefon dla właściciela, który nie znajduje się obecnie w bazie danych, musisz przejść do tyłu i wstawić wiersz bezpośrednio do Phone
. Niezbyt ładne.