@Entity public class Person { @ElementCollection @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID")) private List locations; [...] } @Embeddable public class Location { [...] }
Dada a estrutura de classs a seguir, quando tento adicionar um novo local à lista de locais da pessoa, sempre resulta nas seguintes consultas SQL:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
E
A lotsa' inserts into the PERSON_LOCATIONS table
O Hibernate (3.5.x / JPA 2) exclui todos os registros associados da Pessoa especificada e reinserem todos os registros anteriores, além do novo.
Eu tive a idéia de que o método equals / hashcode no Location resolveria o problema, mas isso não alterou nada.
Quaisquer sugestões são apreciadas!
O problema é explicado de alguma forma na página sobre ElementCollection
do ElementCollection
do JPA:
Chaves primárias no CollectionTable
A especificação do JPA 2.0 não fornece uma maneira de definir o
Id
noEmbeddable
. No entanto, para excluir ou atualizar um elemento do mapeamentoElementCollection
, normalmente é necessária uma chave exclusiva. Caso contrário, em cada atualização, o provedor JPA precisará excluir tudo daCollectionTable
daEntity
e, em seguida, inserir os valores novamente. Portanto, o provedor JPA provavelmente presumirá que a combinação de todos os campos noEmbeddable
seja exclusiva, em combinação com a chave estrangeira (JoinColunm
(s)). No entanto, isso pode ser ineficiente ou simplesmente inviável se oEmbeddable
for grande ou complexo.
E é exatamente (a parte em negrito) o que acontece aqui (o Hibernate não gera uma chave primária para a tabela de coleta e não tem como detectar qual elemento da coleção foi alterado e irá deletar o conteúdo antigo da tabela para inserir o novos conteúdos).
No entanto, se você definir um @OrderColumn
(para especificar uma coluna usada para manter a ordem persistente de uma lista – o que faria sentido desde que você esteja usando uma List
), o Hibernate criará uma chave primária (feita da coluna order e join column ) e poderá atualizar a tabela de coleta sem excluir todo o conteúdo.
Algo parecido com isto (se você quiser usar o nome da coluna padrão):
@Entity public class Person { ... @ElementCollection @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID")) @OrderColumn private List locations; ... }
Além da resposta de Pascal, você também deve definir pelo menos uma coluna como NOT NULL :
@Embeddable public class Location { @Column(name = "path", nullable = false) private String path; @Column(name = "parent", nullable = false) private String parent; public Location() { } public Location(String path, String parent) { this.path = path; this.parent= parent; } public String getPath() { return path; } public String getParent() { return parent; } }
Esse requisito está documentado em AbstractPersistentCollection :
Solução alternativa para situações como HHH-7072. Se o elemento de coleção for um componente que consiste inteiramente em propriedades anuláveis, no momento, precisamos recriar vigorosamente a coleção inteira. Veja o uso de hasNotNullableColumns no construtor AbstractCollectionPersister para mais informações. Para excluir linha por linha, isso exigiria SQL como “WHERE (COL =? OR (COL é nulo AND? É nulo))”, em vez do atual “WHERE COL =?” (falha por nulo para a maioria dos bancos de dados). Note que o parâmetro teria que ser ligado duas vezes. Até que, eventualmente, adicionemos conceitos de “pontos de binding de parâmetro” ao AST no ORM 5+, o manuseio desse tipo de condição é extremamente difícil ou impossível. Forçar a recreação não é o ideal, mas não é realmente outra opção no ORM 4.