Hibernate – @ElementCollection – Estranho comportamento de exclusão / inserção

@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 no Embeddable . No entanto, para excluir ou atualizar um elemento do mapeamento ElementCollection , normalmente é necessária uma chave exclusiva. Caso contrário, em cada atualização, o provedor JPA precisará excluir tudo da CollectionTable da Entity e, em seguida, inserir os valores novamente. Portanto, o provedor JPA provavelmente presumirá que a combinação de todos os campos no Embeddable seja exclusiva, em combinação com a chave estrangeira ( JoinColunm (s)). No entanto, isso pode ser ineficiente ou simplesmente inviável se o Embeddable 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; ... } 

Referências

  • Especificação JPA 2.0
    • Seção 11.1.12 “ElementCollection Annotation”
    • Seção 11.1.39 “Anotação OrderColumn”
  • JPA Wikibook
    • Persistência Java / ElementCollection

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.