PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Mappatura di più righe JPA con ElementCollection

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.