sql >> Base de Datos >  >> RDS >> PostgreSQL

JPA Mapeo de filas múltiples con ElementCollection

Lamentablemente, creo que la ligera diferencia de que solo tienes una mesa es el problema aquí.

Mire la declaración del PhoneId class (que sugeriría que se llame mejor PhoneOwner o algo así):

@Entity
@Table(name="Phones")
public class PhoneId {

Cuando declara que una clase es una entidad asignada a una determinada tabla, está realizando un conjunto de afirmaciones, de las cuales dos son particularmente importantes aquí. En primer lugar, que haya una fila en la tabla para cada instancia de la entidad y viceversa. En segundo lugar, que haya una columna en la tabla para cada campo escalar de la entidad, y viceversa. Ambos están en el corazón de la idea del mapeo relacional de objetos.

Sin embargo, en su esquema, ninguna de estas afirmaciones se cumple. En los datos que diste:

OWNER_ID    TYPE      NUMBER
  1         home      792-0001
  1         work      494-1234
  2         work      892-0005

Hay dos filas correspondientes a la entidad con owner_id 1, violando la primera afirmación. Hay columnas TYPE y NUMBER que no están asignados a campos en la entidad, violando la segunda aserción.

(Para ser claros, no hay nada malo con su declaración del Phone clase o los phones campo:solo el PhoneId entidad)

Como resultado, cuando su proveedor de JPA intente insertar una instancia de PhoneId en la base de datos, se encuentra con problemas. Porque no hay asignaciones para TYPE y NUMBER columnas en PhoneId , cuando genera el SQL para la inserción, no incluye valores para ellos. Esta es la razón por la que recibe el error que ve:el proveedor escribe INSERT INTO Phones (owner_id) VALUES (?) , que PostgreSQL trata como INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null) , que es rechazado.

Incluso si lograra insertar una fila en esta tabla, tendría problemas para recuperar un objeto de ella. Digamos que solicitó la instancia de PhoneId con owner_id 1. El proveedor escribiría SQL equivalente a select * from Phones where owner_id = 1 , y esperaría encontrar exactamente una fila, que puede asignar a un objeto. ¡Pero encontrará dos filas!

La solución, me temo, es usar dos tablas, una para PhoneId y uno para Phone . La tabla para PhoneId será trivialmente simple, pero es necesario para el correcto funcionamiento de la maquinaria JPA.

Suponiendo que cambie el nombre de PhoneId a PhoneOwner , las tablas deben parecerse 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)
)

(He hecho (owner_id, number) la clave principal para Phone , en el supuesto de que un propietario puede tener más de un número de un tipo dado, pero nunca tendrá un número registrado bajo dos tipos. Es posible que prefiera (owner_id, type) si eso refleja mejor su dominio).

Las entidades son entonces:

@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;
}

Ahora, si realmente no desea introducir una tabla para el PhoneOwner , entonces es posible que pueda salir de él usando una vista. Así:

create view PhoneOwner as select distinct owner_id from Phone;

Por lo que el proveedor de JPA puede decir, esta es una tabla y admitirá las consultas que necesita hacer para leer datos.

Sin embargo, no admitirá inserciones. Si alguna vez necesita agregar un teléfono para un propietario que no está actualmente en la base de datos, deberá ir por la parte de atrás e insertar una fila directamente en Phone . No muy agradable.