Purtroppo, penso che la leggera differenza che tieni solo un tavolo sia il problema qui.
Guarda la dichiarazione del PhoneId
class (che suggerirei sia meglio chiamata PhoneOwner
o qualcosa del genere):
@Entity
@Table(name="Phones")
public class PhoneId {
Quando dichiari che una classe è un'entità mappata su una determinata tabella, stai facendo una serie di asserzioni, di cui due sono particolarmente importanti qui. In primo luogo, nella tabella è presente una riga per ciascuna istanza dell'entità e viceversa. In secondo luogo, nella tabella è presente una colonna per ogni campo scalare dell'entità e viceversa. Entrambi sono al centro dell'idea di mappatura relazionale a oggetti.
Tuttavia, nel tuo schema, nessuna di queste affermazioni vale. Nei dati che hai fornito:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Ci sono due righe corrispondenti all'entità con owner_id
1, violando la prima affermazione. Ci sono colonne TYPE
e NUMBER
che non sono mappati ai campi nell'entità, violando la seconda asserzione.
(Per essere chiari, non c'è niente di sbagliato nella tua dichiarazione del Phone
classe o i phones
campo - solo il PhoneId
entità)
Di conseguenza, quando il tuo provider JPA tenta di inserire un'istanza di PhoneId
nel database, ha problemi. Perché non ci sono mappature per il TYPE
e NUMBER
colonne in PhoneId
, quando genera l'SQL per l'inserimento, non include i relativi valori. Questo è il motivo per cui ricevi l'errore che vedi:il provider scrive INSERT INTO Phones (owner_id) VALUES (?)
, che PostgreSQL tratta come INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
, che viene rifiutato.
Anche se riuscissi a inserire una riga in questa tabella, avresti problemi a recuperare un oggetto da essa. Supponiamo che tu abbia chiesto l'istanza di PhoneId
con owner_id
1. Il provider scriverà un codice SQL pari a select * from Phones where owner_id = 1
e si aspetterebbe che trovi esattamente una riga, che può mappare a un oggetto. Ma troverà due righe!
La soluzione, temo, è usare due tabelle, una per PhoneId
e uno per Phone
. La tabella per PhoneId
sarà banalmente semplice, ma è necessario per il corretto funzionamento dei macchinari dell'APP.
Supponendo che tu rinomini PhoneId
a PhoneOwner
, le tabelle devono assomigliare a:
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)
)
(Ho creato (owner_id, number)
la chiave primaria per Phone
, partendo dal presupposto che un proprietario possa avere più di un numero di un dato tipo, ma non avrà mai un numero registrato in due tipi. Potresti preferire (owner_id, type)
se rispecchia meglio il tuo dominio.)
Le entità sono quindi:
@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;
}
Ora, se davvero non vuoi introdurre una tabella per PhoneOwner
, potresti riuscire a uscirne utilizzando una vista. In questo modo:
create view PhoneOwner as select distinct owner_id from Phone;
Per quanto può dire il provider JPA, questa è una tabella e supporterà le query che deve eseguire per leggere i dati.
Tuttavia, non supporterà gli inserti. Se hai mai avuto bisogno di aggiungere un telefono per un proprietario che non è attualmente nel database, dovresti andare sul retro e inserire una riga direttamente in Phone
. Non molto bello.