PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

JPA Mapeamento de várias linhas com ElementCollection


Infelizmente, acho que a pequena diferença de você manter apenas uma tabela é o problema aqui.

Veja a declaração do PhoneId class (que eu sugiro que seja melhor chamada PhoneOwner ou algo assim):
@Entity
@Table(name="Phones")
public class PhoneId {

Quando você declara que uma classe é uma entidade mapeada para uma determinada tabela, você está fazendo um conjunto de asserções, das quais duas são particularmente importantes aqui. Em primeiro lugar, que existe uma linha na tabela para cada instância da entidade e vice-versa. Em segundo lugar, que existe uma coluna na tabela para cada campo escalar da entidade e vice-versa. Ambos estão no centro da ideia de mapeamento objeto-relacional.

No entanto, em seu esquema, nenhuma dessas afirmações é válida. Nos dados que você deu:
OWNER_ID    TYPE      NUMBER
  1         home      792-0001
  1         work      494-1234
  2         work      892-0005

Existem duas linhas correspondentes à entidade com owner_id 1, violando a primeira asserção. Existem colunas TYPE e NUMBER que não são mapeados para campos na entidade, violando a segunda asserção.

(Para ser claro, não há nada de errado com sua declaração do Phone classe ou os phones campo - apenas o PhoneId entidade)

Como resultado, quando seu provedor JPA tenta inserir uma instância de PhoneId no banco de dados, ele tem problemas. Como não há mapeamentos para o TYPE e NUMBER colunas em PhoneId , ao gerar o SQL para a inserção, não inclui valores para eles. É por isso que você recebe o erro que vê - o provedor escreve INSERT INTO Phones (owner_id) VALUES (?) , que o PostgreSQL trata como INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null) , que é rejeitado.

Mesmo se você conseguisse inserir uma linha nessa tabela, teria problemas para recuperar um objeto dela. Digamos que você tenha solicitado a instância de PhoneId com owner_id 1. O provedor escreveria SQL equivalente a select * from Phones where owner_id = 1 , e esperaria que encontrasse exatamente uma linha, que pudesse mapear para um objeto. Mas ele vai encontrar duas linhas!

A solução, receio, é usar duas tabelas, uma para PhoneId , e um para Phone . A tabela para PhoneId será trivialmente simples, mas é necessário para o correto funcionamento do maquinário JPA.

Supondo que você renomeie PhoneId para PhoneOwner , as tabelas precisam ser parecidas com:
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)
)

(Eu fiz (owner_id, number) a chave primária para Phone , na suposição de que um proprietário pode ter mais de um número de um determinado tipo, mas nunca terá um número registrado em dois tipos. Você pode preferir (owner_id, type) se isso refletir melhor seu domínio.)

As entidades são então:
@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;
}

Agora, se você realmente não quer introduzir uma tabela para o PhoneOwner , então você poderá sair dele usando uma view. Assim:
create view PhoneOwner as select distinct owner_id from Phone;

Até onde o provedor JPA pode dizer, esta é uma tabela e suportará as consultas necessárias para ler os dados.

No entanto, ele não suportará inserções. Se você já precisou adicionar um telefone para um proprietário que não está no banco de dados, você precisa dar a volta e inserir uma linha diretamente em Phone . Não muito legal.