Pular para o conteúdo

Implementando PFK no JPA com Hibernate

fevereiro 18, 2011

Algumas vezes precisamos configurar o JPA para respeitar um relacionamento um para um da base de dados que está modelado com um campo PFK na tabela filha do relacionamento.

Tive a necessidade de configurar um relacionamento deste tipo utilizando o JPA com Hibernate, após muito googlar e achar somente um post que explica como se faz essa configuração resolvi escrever este post explicando minha experiência.

O Problema

Vamos considerar que temos um relacionamento um para um na base de dados envolvendo a tabela pai Usuario e a tabela filha LDAP.

Este relacionamento foi necessário devido ao fato que a tabela LDAP armazena um campo do tipo CLOB. Este campo poderia estar na tabela Usuario evitando ter mais uma tabela no modelo de dados, mas podemos analisar alguns problemas em ter este campo na mesma tabela usando o JPA. Segue abaixo duas situações que afetam a performance da aplicação na utilização do campo CLOB na tabela Usuario:

  • Utilização do find(id): Se a aplicação faz muitas chamadas ao método find do EntityManager para recuperar os dados do usuário o JPA sempre traz o campo CLOB para memória. Muitas vezes não é necessário que o JPA traga o campo CLOB pois ele não é utilizado sempre, isso afeta diretamente a performance da aplicação.
  • Utilizaçao de JPA-QL ou HQL: Para consultas utilizando JPA-QL que envolve a entidade Usuario e outras entidades o Hibernate pode executar um ou mais join(s) entre as tabelas. Se temos uma tabela com um campo CLOB em uma consulta complexa que envolve muitos joins a consulta é prejudicada em performance.

Por isso uma das soluções para este problema é separar o(s) campo(s) CLOB ou BLOB em outra tabela utilizando um relacionamento um para um entre as tabelas.

Vamos à prática

Abaixo segue o código da entidade Usuario:

public class Usuario {

    @Id
    @GeneratorValue(strategy=StrategyType.AUTO)
    @Column(name="usuario_id")
    private Long id;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinColumn(name="Usuario_id", insertable = true, updatable = false) 
    private LDAP ldap;

    // os getters e setters

}

Abaixo segue o código da entidade LDAP:

public class LDAP {

    @Id
    @GeneratorValue(generator = "myForeignGenerator")
    @GenericGenerator(name = "myForeignGenerator", strategy = "foreign", 
                            parameters = { 
                                @Parameter(name = "property", value = "usuario") 
                            })
    @Column(name = "usuario_id")
    private Long id;

    @Lob
    @Column(name = "ldap")
    private 

    @OneToOne(optional = false)
    @PrimaryKeyJoinColumn
    private Usuario usuario;

    // os getters e setters
    
}

Explicando o código

Para mapear o relacionamento PFK (PrimaryForeignKey) usando anotações você deve anotar com @PrimaryKeyJoinColumn o campo usuario da entidade LDAP, esta anotação informa o JPA que este relacionamento está sendo mapeado através do campo que é chave primária da tabela.

O JPA não inclui um método padronizado para tratar a geração de chave primária para o relacionamento PFK, o que significa que você deve ser o responsável por setar o identificador à entidade LDAP antes de savar.

O Hibernate tem uma extensão de anotação que permite customizar um gerador de identificador que pode ser usado para a entidade LDAP. A anotação do Hibernate @GenericGenerator que possibilita criar um gerador customizado que é identificado pela propriedade name, a propriedade strategy deve ser setada para foreign e deve ter a anotação @Parameter para identificar que a foreign key está sendo mapeada pelo atributo usuario.

Fácil assim você pode configurar um relacionamento OneToOne que representa uma PFK na base de dados. Lembre-se que o Hibernate não sabe setar um valor para o @Id da entidade LDAP, pois anotamos o @Id com um GeneratorValue customizado. Portanto, não se esqueça de setar o @Id pela aplicação.

Observações

O JPA permite que um atributo de uma entidade seja anotado com @Basic(fetch = FetchType.LAZY), mas este atributo deve ser um campo da tabela e não um relacionamento.

Sendo assim podemos supor que anotando dessa forma o campo Usuario.ldap o JPA vai trazer o campo somente quando for utilizado e não em todas as consultas á entidade, mas isso não acontece.

Pesquisando a respeito desta anotação eu li no livro Java Persistence with Hibernate que não são todos os banco de dados que implementam esta funcionalidade de LAZY em um campo CLOB ou BLOB o único que implementa é o PostgreSQL.

O relacionamento PFK na base de dados não é muito conhecido e pouco usado, mas nunca sabemos quando ele vai aparecer em nossas vidas para atormentar nossa configuração de mapeamento do JPA. Este tipo de relacionamento também é conhecido como Shared Primary Key.

Bom fica ai a dica pra vocês que estão precisando desta característica na aplicação. Até a proxima pessoal! 😀

Paulo R. A. Sales – @salespaulo

2 Comentários
  1. Leonardo permalink

    Ótimo post! me ajudou muito 😀

  2. Cara muito bom! Tenho uma situação muito parecida com essa. No meu projeto eu utilizo esse tipo de relacionamento para gravar numa Entidade Usuario com todos os dados referentes ao Login daquele usuario, que é uma especialização de uma entidade chamada Pessoa, onde eu tenho o campo chave primária chamado idPessoa, ou seja, para mapear este relacionamento utilizei da mesma forma que você criou. Mas agora com sua explicação tudo ficou muito claro como funciona a anotação @GenericGenerator, que possibilita criar um gerador customizado. Porque como você disse o JPA não inclui um método especifico para tratar esse tipo de relacionamento. No meu modelo eu crio um relacionamento de um para um identificante que resulta num campo do tipo PFK na tabela usuario.

Deixe um comentário