JPA - OneToOne

Relacionamento Um-para-Um

Este relacionamento informa que há apenas um registro da entidade relacionado com um registro de outra entidade.

Exemplo de relacionamento Um-para-Um unidirecional, no qual temos uma Mensagem dividida em duas tabelas:

Script do banco de dados:

CREATE TABLE Mensagem (
  id int NOT NULL auto_increment,
  assunto varchar(200),
  dataEnvio date,
  mensagemcorpo_id int,
  PRIMARY KEY (id)
);

CREATE TABLE MensagemCorpo (
  id int(6) NOT NULL auto_increment,
  descricao varchar(1000),
  PRIMARY KEY (id)
);

Modelo UML:

Exemplo de relacionamento @OneToOne.

Neste exemplo definimos que uma Mensagem possui uma MensagemCorpo, então destaforma a Mensagem sabe qual é seu MensagemCorpo, mas o contrario não existe, a MensagemCorpo não tem a necessidade de conhecer qual a Mensagem está associado a ele, ou seja, temos um relacionamento unidirecional.

Primeiramente podemos mostrar apenas uma listagem de Mensagens, mas não tem necessidade por enquanto de mostrar o conteúdo de todas as mensagens e depois caso eu queira ler o conteúdo da mensagem podemos através dela chegar até seu corpo utilizando o atributo do tipo MensagemCorpo, ou seja, encontrar o conteúdo da mensagem.

Código fonte das classes com o relacionamento:

package br.universidadejava.jpa.exemplo.modelo;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * Classe utilizada para representar uma Mensagem.
 */
@Entity
public class Mensagem implements Serializable {
  private static final long serialVersionUID = 1912492882356572322L;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String assunto;
  @Temporal(TemporalType.DATE)
  private Date dataEnvio;
  @OneToOne(cascade=CascadeType.ALL)
  private MensagemCorpo mensagemCorpo;

  public String getAssunto() { return assunto; }
  public void setAssunto(String assunto) { this.assunto = assunto; }
  public Date getDataEnvio() { return dataEnvio; }
  public void setDataEnvio(Date dataEnvio) { this.dataEnvio = dataEnvio; }
  public Long getId() { return id; }
  public void setId(Long id) { this.id = id; }
  public MensagemCorpo getMensagemCorpo() { return mensagemCorpo; }
  public void setMensagemCorpo(MensagemCorpo mensagemCorpo) {
    this.mensagemCorpo = mensagemCorpo;
  }
}

Na classe Mensagem utilizamos a anotação javax.persistence.OneToOne para definir o relacionamento de um-para-um entre as classes Mensagem e MensagemCorpo.

package br.universidadejava.jpa.exemplo.modelo;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
 * Classe utilizada para representar o corpo de uma mensagem.
 */
@Entity
public class MensagemCorpo implements Serializable {
  private static final long serialVersionUID = 986589124772488369L;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String descricao;

  public String getDescricao() { return descricao; }
  public void setDescricao(String descricao) { this.descricao = descricao; }
  public Long getId() { return id; }
  public void setId(Long id) { this.id = id; }
}

A classe MensagemCorpo é uma entidade normal que não conhece a classe Mensagem.

Note que não precisamos criar nenhuma referencia para informar que o atributo mensagemCorpo da classe Mensagem referencia a coluna mensagemcorpo_id da tabela Mensagem.

O JPA possui alguns padrões para facilitar o mapeamento entre a classe Java e a tabela do banco de dados, quando criamos uma coluna de chave estrangeira seguindo o padrão nometabela_chaveprimaria, o mapeamento é feito automaticamente pelo JPA, ou seja, o atributo MensagemCorpo mensagemCorpo é automaticamente associado com a coluna mensagemcorpo_id.

A classe MensagemDAO ficará da seguinte forma:

package br.universidadejava.jpa.exemplo.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import pbc.jpa.exemplo.modelo.Mensagem;

public class MensagemDAO {

  /**
   * Método utilizado para obter o entity manager.
   * @return
   */
  private EntityManager getEntityManager() {
    EntityManagerFactory factory = null;
    EntityManager entityManager = null;
    try {
      //Obtém o factory a partir da unidade de persistência.
      factory = Persistence.createEntityManagerFactory("MensagemPU");
      //Cria um entity manager.
      entityManager = factory.createEntityManager();
      //Fecha o factory para liberar os recursos utilizado.
    } catch(Exception ex) {
      System.out.println("ERRO");
      ex.printStackTrace();
    } finally {
      factory.close();
    }
    return entityManager;
  }

  /**
   * Método utilizado para salvar ou atualizar as informações de uma mensagem.
   * @param mensagem
   * @return
   * @throws java.lang.Exception
   */
  public Mensagem salvar(Mensagem mensagem) throws Exception {
    EntityManager entityManager = getEntityManager();
    try {
      // Inicia uma transação com o banco de dados.
      entityManager.getTransaction().begin();
      System.out.println("Salvando a mensagem.");
      // Verifica se a mensagem ainda não está salva no banco de dados.
      if (mensagem.getId() == null) {
        //Salva os dados da mensagem.
        entityManager.persist(mensagem);
      } else {
        //Atualiza os dados da mensagem.
        mensagem = entityManager.merge(mensagem);
      }
      // Finaliza a transação.
      entityManager.getTransaction().commit();
    } finally {
      entityManager.close();
    }
    return mensagem;
  }

  /**
   * Método que apaga a mensagem e mensagem corpor do banco de dados.
   * @param id
   */
  public void excluir(Long id) {
    EntityManager entityManager = getEntityManager();
    try {
      // Inicia uma transação com o banco de dados.
      entityManager.getTransaction().begin();
      // Consulta a mensagem na base de dados através do seu ID.
      Mensagem mensagem = entityManager.find(Mensagem.class, id);
      System.out.println("Excluindo os dados de: " + mensagem.getAssunto());
      // Remove a mensagem e a mensagem corpor da base de dados.
      entityManager.remove(mensagem);
      // Finaliza a transação.
      entityManager.getTransaction().commit();
    } finally {
      entityManager.close();
    }
  }

  /**
   * Consulta o mensagem pelo ID.
   * @param id
   * @return o objeto Mensagem.
   */
  public Mensagem consultarPorId(Long id) {
    EntityManager entityManager = getEntityManager();
    Mensagem mensagem = null;
    try {
      //Consulta uma mensagem pelo seu ID.
      mensagem = entityManager.find(Mensagem.class, id);
    } finally {
      entityManager.close();
    }
    return mensagem;
  }
}

No exemplo a seguir estamos chamando apenas o método salvar da classe MensagemDAO:

package br.universidadejava.jpa.exemplo.teste;

import java.util.Date;
import br.universidadejava.jpa.exemplo.dao.MensagemDAO;
import br.universidadejava.jpa.exemplo.modelo.Mensagem;
import br.universidadejava.jpa.exemplo.modelo.MensagemCorpo;

public class MensagemTeste {
  public static void main(String[] args) {
    try {
      // Cria a mensagem.
      Mensagem msg = new Mensagem();
      msg.setAssunto("Exemplo de relacionamento OneToOne");
      msg.setDataEnvio(new Date());

      // Cria o corpo (conteúdo) da mensagem.
      MensagemCorpo corpo = new MensagemCorpo();
      corpo.setDescricao("O relacionamento @OneToOne informa que há apenas um registro da entidade relacionado com um registro de outra entidade, neste exemplo uma Mensagem tem apenas um MensagemCorpo.");
      msg.setMensagemCorpo(corpo);

      MensagemDAO dao = new MensagemDAO();
      // Salva a mensagem no banco de dados.
      msg = dao.salvar(msg);
      System.out.println("Salvo a mensagem: " + msg.getId() + " - " + msg.getAssunto());
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

Ao salvar a mensagem no banco de dados, também será salvo automaticamente a mensagem corpo nas devidas tabelas:

Salvando a mensagem.

Hibernate: insert into MensagemCorpo (descricao, id) values (?, ?)
Hibernate: insert into Mensagem (assunto, dataEnvio, mensagemCorpo_id, id) values (?, ?, ?, ?)

Salvo a mensagem: 1 - Exemplo de relacionamento OneToOne

javax.persistence.OneToOne

Esta anotação define uma associação com outra entidade que tenha a multiplicidade de um-para-um.

Propriedades Descrição
cascade As operações que precisam ser refletidas no alvo da associação.
fetch Informa se o alvo da associação precisa ser obtido apenas quando for necessário ou se sempre deve trazer.
mappedBy Informa o atributo que é dono do relacionamento.
optional Informa se a associação é opcional.
targetEntity A classe entity que é alvo da associação.

Quando precisamos especificar um mapeamento que não é padrão do JPA, podemos utilizar a anotação javax.persistence.JoinColumn, por exemplo se a tabela Mensagem e MensagemCorpo fossem:

CREATE TABLE Mensagem (
  id int NOT NULL auto_increment,
  assunto varchar(200),
  dataEnvio date,
  ID_MENSAGEMCORPO int,
  PRIMARY KEY (id)
);

CREATE TABLE MensagemCorpo (
  MC_ID int(6) NOT NULL auto_increment,
  descricao varchar(200),
  PRIMARY KEY (MC_ID)
);

Poderíamos utilizar o JoinColumn para criar a associação:

@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name = "ID_MENSAGEMCORPO", referencedColumnName = "MC_ID")
private MensagemCorpo mensagemCorpo;

javax.persistence.JoinColumn

Esta anotação é utilizada para especificar a coluna utilizada na associação com outra entity.

Propriedades Descrição
columnDefinition Definição do tipo da coluna.
insertable Informa se a coluna é incluída no SQL de INSERT.
name Informa o nome da coluna de chave estrangeira.
nullable Informa se a coluna pode ser null.
referencedColumnName Nome da coluna que é referenciada pela coluna da chave estrangeira.
table Nome da tabela que contém a coluna.
unique Informa se a propriedade é chave única.
updatable Informa se a coluna é incluída no SQL de UPDATE.