JPA @OneToOne Unidirecional e Bidirecional

EXISTE UMA NOVA VERSÃO DESSE POST.
CLICK AQUI: http://uaihebert.com/?p=1622&page=19

Olá, bom dia.

Vamos falar hoje sobre o relacionamento @OneToOne unidirecional e bidirecional. Diversas vezes precisamos mapear diferentes relacionamentos e o JPA facilita muito esse mapeamento através de anotações.

Iremos usar um simples exemplo, um Customer (cliente) tem um User (usuário). Onde um Customer pode ter apenas um User e vice-versa, um simples um para um.

Utilizarei o código dos outros artigos já utilizados. Caso você queira criar um projeto para executar o código aqui exibido, você poderá achar os passos necessários (configurações, downloads, tutoriais) nos primeiros posts sobre o assunto: Mapeando Duas Tabelas em uma Classe, Mapeando Datas (Date) e Enum, Chave Primária Composta, SequenceGenerator, TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 com JPA 2.

Vamos ver primeiro como ficaria o relacionamento Unidirecional?

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "CUSTOMER")
@SequenceGenerator(name = "CUSTOMER_SEQUENCE", sequenceName = "CUSTOMER_SEQUENCE", allocationSize = 1, initialValue = 0)
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_SEQUENCE")
    private int id;

    @Column
    private String name;

    @OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.EAGER, orphanRemoval = true)
    // @JoinColumn(name="USER_ID", nullable=false)
    @PrimaryKeyJoinColumn
    private User user;

    //Getters and Setters
}
package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="USER")
@SequenceGenerator(name="USER_SEQUENCE", sequenceName="USER_SEQUENCE", allocationSize=1, initialValue=0)
public class User{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="USER_SEQUENCE")
    private int id;

    @Column
    private String login;

    @Column
    private String password;
    // Getters and Setters
}

Sobre os parâmetros da anotação @OneToOne:

  • “cascade” – define ações automatizadas no relacionamento, ex.: Ao apagar um Customer, apagar também um Usuário. Veremos sobre “cascade” em um post futuro.
  • “optional” – você não será obrigado a ter um User ao persistir um Customer, ou seja, você não tem que criar um usuário para satisfazer essa condição (você poderá buscar o Customer no banco de dados, mas Customer.getUser() terá null como resposta). Com o valor igual a false, ao se cadastrar um Customer, é obrigatória a presença de um User. Pode ser um User recém ainda não persistido.
  • “fetch” – o valor padrão é EAGER. Ou seja, ao carregar o Customer já será feita a consulta relacionada ao User de modo automático. Iremos ver sobre esse assunto em um post futuro.
  • “orphanRemoval” – define que uma entidade dependente, caso não tenha relacionamento, será removida do banco de dados (em nosso modelo, User depende de Customer). Caso exista um User sem Customer, esse user será removido.

Existem também duas anotações sendo que uma delas está comentada:

  • // @JoinColumn(name=”USER_ID”, nullable=false) – define qual é a coluna mapeada para fazer a união na consulta. É indicado o nome da coluna através do parâmetro “name” e que esse campo não pode ser nulo pelo parâmetro “nullable”.
  • @PrimaryKeyJoinColumn – essa anotação indica ao JPA que, para encontrar um objeto User basta procurar um registro com o mesmo ID do Customer. Ou seja, indica que um User vai ter o mesmo ID que seu Customer.

Para fazer um teste, execute o código abaixo para ver que será criado um User e um Customer; mesmo o comando de “persist” sendo executado apenas com o Customer:

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Customer customer = new Customer();
            customer.setName("John Doe");

            User user = new User();
            user.setLogin("jDoe");
            user.setPassword("123changeME!@#");

            customer.setUser(user);

            em.persist(customer);

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Imagem Console.

Realizando uma consulta, eu utilizei o método “find”, você irá encontrar o seguinte resultado:

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Customer customer = em.find(Customer.class, 1);

            System.out.println(customer.getName());
            System.out.println(customer.getUser().getLogin());

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Imagem Console.

Note que através do Customer conseguimos buscar o usuário relacionado.

Para fazer nosso relacionamento bidirecional vamos alterar nossa classe User:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "USER")
@SequenceGenerator(name = "USER_SEQUENCE", sequenceName = "USER_SEQUENCE", allocationSize = 1, initialValue = 0)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_SEQUENCE")
    private int id;

    @Column
    private String login;

    @Column
    private String password;

    @OneToOne(mappedBy = "user")
    private Customer customer;

    //Getters and Setters
}

Repare que agora temos uma referência à classe Customer e com mapeamento @OneToOne. O parâmetro “mappedBy” indica quem é o owner desse relacionamento, o lado mais forte. Repare que não foi necessário repetir nenhum código do mapeamento pois em nossa classe Customer já está tudo configurado.

Qual a diferença de um relacionamento unidirecional e um bidirecional? Repare que na Unidirecional apenas Customer tem referência User, ou seja, só é possível fazer Customer.getUser(). Como alteramos nossa classe User agora podemos fazer User.getCustomer().

Resumindo, um relacionamento bidirecional é quando duas classes de um mapeamento contêm referências mútuas. User TEM-UM Customer e Customer TEM-UM User.

Mas, para criar persistir no banco de dados é diferente. Vamos ver como ficará nossa classe Main para criar um registro:

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Customer customer = new Customer();
            customer.setName("John Doe");

            User user = new User();
            user.setLogin("jDoe");
            user.setPassword("123changeME!@#");

            // You must define a bidirectional relationship
			// like this
			customer.setUser(user);
            user.setCustomer(customer);

            em.persist(customer);

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Como diferença note que foi necessário utilizar o método User.setCustomer(Customer) e também Customer.setUser(User). Lembre-se que o java em si trabalha com referências, então precisamos sim apontar uma classe para a outra.

E depois, basta consultar em qualquer lado do relacionamento. Vamos consultar pelo User agora? Veja como nosso código ficará:

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            User user = em.find(User.class, 2);

            System.out.println(user.getLogin());
            System.out.println(user.getCustomer().getName());

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Imagem do Console.

Espero que esse post possa ter te ajudado.

Qualquer dúvida/sugestão basta colocar.

Até a próxima.

20 thoughts on “JPA @OneToOne Unidirecional e Bidirecional

  1. Olá uaihebert tudo bom?

    Trabalho já um tempo com EJB + JPA 2.0 no netbeans, agora estou experimentando o eclipse com JPA + Hibernate com plugin Daly e Hibernate Tools, estou enfrentando o seguinte problema, em um relacionamento oneToOne de duas tabelas ex: pessoa X pessoa_cliente, quando importo as entidades do banco de dados, o relacionamento uni-direcional fica com erro, enfim não consigo atraves da classe PessoaCliente obter a Pessoa relacionada.

    Você já enfrentou algum problema assim, tem alguma ideia de solução?

    Agradeço desde já pela sua atenção.

    Abraços.

    • Gente pode ser antigo o post mas fica um duvida…

      a tabela n fez a referencia errada,
      pessoa_cliente deveria existir ??
      n seria um oneToMany entre pessoaXcliente com joinTable?

      com isso a tabela pessoa_cliente ficaria implicita nos join

      Abraço

      • Olá Pablo, boa tarde.

        JoinTable só é utilizado em dois casos. Em um relacionamento @ManyToMany ou em um @OneToMany Unidirecional.

        No caso de OneToOne ele cria apenas chave estrangeira do lado dominante da relação. Ou então, pode-se criar uma chave estrangeira dos dois lados caso não exista o elemento mappedBy em algumas das Tags.

        Espero ter ajudado e obrigado pela visita.

  2. Olá,

    No contexto do relacionamento unidirecional, eu consigo através do uso de anotação que um Customer seja excluído quando o User a ele relacionado é excluído?
    Mexi com targetEntity, cascade, orphanRemoval, entre outras anotações e não consegui obter sucesso.

    Desde já agradeço.
    Att,
    Carlos Eduardo.

    • Olá Carlos, bom dia.

      Até onde me recordo, quando o relacionamento é unidirecional apenas o lado dominante (que contém a foreign key) tem como excluir utilizando cascade.

      Você poderia no método de excluir o User localizar o Customer e excluir o Customer ao invés do User.

      Eu estou a ler um livro focado em JPA e caso encontre alguma alternativa “automática” eu posso te falar.

      Att,

  3. Eis que pesquiso pelo Google sobre relacionamentos @OneToOne bidirecionais e qual é o site que encontro na 1a posição de pesquisa ??! Uaihebert ! ehheeh

    Parabéns pelo site cara, post bem explicativo !

    Falows!
    David

  4. Boa noite amigo, Primeiramente gostaria de te parabenizar pela excelente tutorial disponibilizado. Por ultimo gostaria que me esclarecesse algumas duvidas.
    Supondo que temos uma entidade Estado e outra Cidade.
    Um estado é composto por varias cidades, e uma cidade esta presente em um estado. Levando isso em consideração como ficaria o mappedby? Se de estado para cidade a relação é OneToMany, em estado eu tenho que especificar OneToOne ou ManyToOne? Me desculpa caso não venha a inteder minha pergunta, é que ainda estou meio confuso com as anotações.
    Boa noite!

    • Joel, bom dia.

      Um estado é composto por varias cidades, e uma cidade esta presente em um estado.

      Repare que você está falando de uma lista de cidades, desse modo, a lista é OneToMany. A outra ponta será ManyToOne pois é um objeto único que aponta para vários objetos.

      Espero ter ajudado.

      • Muito obrigado pelo esclarecimento!
        Outra duvida, por acaso sou obrigado a especificar o relacionamento das duas pontas? Ou eu posso deixar apenas unidirecional? É que o mesmo esta me retornando um error!
        Desde já agradeço.
        Tenha um bom dia!

  5. Olá Hebert,

    Primeiramente gostaria de parabeniza-lo pelo blog, tem um ótimo conteúdo.
    Pois bem , vamos ao problema.

    Tenho um relacionamento OneToOne bidereicional.
    Porém quando tento persistir o lado fraco do relacionamento o JPA duplica os registros do lado forte. Você saberia dizer o porque dessa situação ?
    Abaixo o código:
    @OneToOne(mappedBy=”pessoa” , cascade = CascadeType.ALL)
    private Status status;

    @OneToOne(cascade = CascadeType.ALL , optional=false , orphanRemoval=true )
    @PrimaryKeyJoinColumn(name=”idStatus” , referencedColumnName=”idStatus”)
    private Pessoa pessoa;
    @Id@Column(unique=true, nullable=false)
    private int idStatus;

    Estou usando a implementação do JPA nativo do glassfish 3.12.

    Atenciosamente.

    • Gonca, boa tarde.

      Eu diria que é pela falta do mappedBy e o uso do Cascade. Todo relacionamento precisa definir quem é o lado dominante, sem ele você pode ter esses tipos de problemas. Veja esse post, ele te ajudará: http://uaihebert.com/?p=1622

      Obrigado pela visita.

  6. customer.setUser(user);
    user.setCustomer(customer);

    em.persist(customer);

    Porque devemos fazer o set nos dois se apenas vamos colocar um no banco??

    • Eduardo, boa noite.

      Existe um parágrafo que explica isso:

      Como diferença note que foi necessário utilizar o método User.setCustomer(Customer) e também Customer.setUser(User). Lembre-se que o java em si trabalha com referências, então precisamos sim apontar uma classe para a outra.

      É por questão de como o java trata relacionamento bidirecional, e não o JPA em si.

      Obrigado pela visita.

        • Eduardo, boa noite.

          Você pode gravar por ambas, mas tem que sempre fazer o relacionamento antes e se necessário configurar o cascade.

          Obrigado pela visita.

Leave a Comment