Exemplo de Mapeamento Um para Um com Hibernate e Anotações

Hoje vamos analisar o Mapeamento Um para Um no Hibernate. Vamos analisar um exemplo de Mapeamento Um para Um no Hibernate usando Anotações e configuração XML.

Mapeamento Um para Um no Hibernate

Na maioria das vezes, as tabelas do banco de dados estão associadas entre si. Existem muitas formas de associação – um para um, um para muitos e muitos para muitos estão no nível mais amplo. Essas podem ser ainda divididas em mapeamentos unidirecionais e bidirecionais. Hoje vamos analisar a implementação do Mapeamento Um para Um do Hibernate usando configuração XML assim como usando configuração de anotações.

Configuração do Exemplo de Banco de Dados do Hibernate Um para Um

Primeiramente, precisaríamos configurar a correspondência um para um nas tabelas do banco de dados. Vamos criar duas tabelas para o nosso exemplo – Transação e Cliente. Ambas essas tabelas terão uma correspondência um para um. A Transação será a tabela principal e utilizaremos a Chave Estrangeira na tabela Cliente para a correspondência um para um. Estou fornecendo o script MySQL, que é o banco de dados que estou usando para este tutorial. Se estiver utilizando outro banco de dados, certifique-se de alterar o script conforme necessário.

-- Criar Tabela de Transação
CREATE TABLE `Transaction` (
  `txn_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `txn_date` date NOT NULL,
  `txn_total` decimal(10,0) NOT NULL,
  PRIMARY KEY (`txn_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

-- Criar tabela de Cliente
CREATE TABLE `Customer` (
  `txn_id` int(11) unsigned NOT NULL,
  `cust_name` varchar(20) NOT NULL DEFAULT '',
  `cust_email` varchar(20) DEFAULT NULL,
  `cust_address` varchar(50) NOT NULL DEFAULT '',
  PRIMARY KEY (`txn_id`),
  CONSTRAINT `customer_ibfk_1` FOREIGN KEY (`txn_id`) REFERENCES `Transaction` (`txn_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

O Diagrama de Relação de Entidades (ERD) da correspondência um para um entre as tabelas parece com a imagem abaixo. Nossa configuração de banco de dados está pronta, vamos avançar para o Projeto de Exemplo do Hibernate One to One agora.

Estrutura do Projeto de Exemplo de Mapeamento Um para Um com Hibernate

Primeiramente, crie um projeto Maven simples no seu IDE Java, estou utilizando o Eclipse. A estrutura final do nosso projeto será semelhante à imagem abaixo.

“`xml
Hibernate Maven Dependencies

Nosso arquivo pom.xml final terá a seguinte aparência.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.journaldev.hibernate</groupId>
  <artifactId>HibernateOneToOneMapping</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
  	<dependency>
  		<groupId>org.hibernate</groupId>
  		<artifactId>hibernate-core</artifactId>
  		<version>4.3.5.Final</version>
  	</dependency>
  	<dependency>
  		<groupId>mysql</groupId>
  		<artifactId>mysql-connector-java</artifactId>
  		<version>5.0.5</version>
  	</dependency>
  </dependencies>
</project>

As dependências são apenas para o Hibernate e o driver Java do MySQL. Observe que estou utilizando a versão mais recente do Hibernate, 4.3.5.Final, e o driver Java do MySQL com base na versão do meu servidor de banco de dados MySQL (5.0.5). O Hibernate 4 utiliza o JBoss logging, que é importado automaticamente como uma dependência transitiva. Você pode confirmar isso nas dependências do Maven do projeto. Se estiver utilizando versões mais antigas do Hibernate, talvez seja necessário adicionar dependências do slf4j.

“`xml
Hibernate One to One Mapping Model Classes

As classes de modelo para o mapeamento One to One do Hibernate, refletindo as tabelas do banco de dados, seriam como mostrado abaixo.

package com.journaldev.hibernate.model;

import java.util.Date;

public class Txn {

	private long id;
	private Date date;
	private double total;
	private Customer customer;
	
	@Override
	public String toString(){
		return id+", "+total+", "+customer.getName()+", "+customer.getEmail()+", "+customer.getAddress();
	}
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}
	public double getTotal() {
		return total;
	}
	public void setTotal(double total) {
		this.total = total;
	}
	public Customer getCustomer() {
		return customer;
	}
	public void setCustomer(Customer customer) {
		this.customer = customer;
	}
	
}
package com.journaldev.hibernate.model;

public class Customer {

	private long id;
	private String name;
	private String email;
	private String address;
	
	private Txn txn;
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public Txn getTxn() {
		return txn;
	}
	public void setTxn(Txn txn) {
		this.txn = txn;
	}
	
}

Uma vez que estamos utilizando uma configuração baseada em XML para o mapeamento, as classes do modelo acima são simples classes POJO ou Java Beans com métodos getter-setter. Estou usando o nome da classe como Txn para evitar confusão, pois a API do Hibernate tem uma classe com o nome Transaction.

Configuração de Mapeamento Hibernate Um para Um

Vamos criar arquivos de configuração de mapeamento hibernate um para um para as tabelas Txn e Customer. txn.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="com.journaldev.hibernate.model.Txn" table="TRANSACTION" >
		<id name="id" type="long">
			<column name="txn_id" />
			<generator class="identity" />
		</id>
		<property name="date" type="date">
			<column name="txn_date" />
		</property>
		<property name="total" type="double">
			<column name="txn_total" />
		</property>
		<one-to-one name="customer" class="com.journaldev.hibernate.model.Customer"
			cascade="save-update" />
	</class>
	
</hibernate-mapping>

O ponto importante a ser observado acima é o elemento hibernate one-to-one para a propriedade do cliente. customer.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>

	<class name="com.journaldev.hibernate.model.Customer" table="CUSTOMER">
		<id name="id" type="long">
			<column name="txn_id" />
			<generator class="foreign">
				<param name="property">txn</param>
			</generator>
		</id>
		<one-to-one name="txn" class="com.journaldev.hibernate.model.Txn"
			constrained="true"></one-to-one>

		<property name="name" type="string">
			<column name="cust_name"></column>
		</property>
		<property name="email" type="string">
			<column name="cust_email"></column>
		</property>
		<property name="address" type="string">
			<column name="cust_address"></column>
		</property>
	</class>

</hibernate-mapping>

classe do gerador=“foreign” é a parte importante usada para a implementação de chave estrangeira no Hibernate.

Arquivo de Configuração do Hibernate

Aqui está o arquivo de configuração do Hibernate para a configuração de mapeamento hibernate baseada em XML. hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.password">pankaj123</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
        <property name="hibernate.connection.username">pankaj</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <property name="hibernate.current_session_context_class">thread</property>
        <property name="hibernate.show_sql">true</property>
        
        <mapping resource="txn.hbm.xml"/>
        <mapping resource="customer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

O arquivo de configuração do Hibernate é simples, contendo propriedades de conexão com o banco de dados e recursos de mapeamento do Hibernate.

Utilitário de SessionFactory do Hibernate

Aqui está a classe utilitária para criar uma instância de SessionFactory do Hibernate.

package com.journaldev.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateUtil {

	private static SessionFactory sessionFactory;
	
	private static SessionFactory buildSessionFactory() {
        try {
            // Criar a SessionFactory a partir do hibernate.cfg.xml
        	Configuration configuration = new Configuration();
        	configuration.configure("hibernate.cfg.xml");
        	System.out.println("Hibernate Configuration loaded");
        	
        	ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        	System.out.println("Hibernate serviceRegistry created");
        	
        	SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        	
            return sessionFactory;
        }
        catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            ex.printStackTrace();
            throw new ExceptionInInitializerError(ex);
        }
    }
	
	public static SessionFactory getSessionFactory() {
		if(sessionFactory == null) sessionFactory = buildSessionFactory();
        return sessionFactory;
    }
}

É isso, vamos escrever um programa de teste para testar o mapeamento um para um do Hibernate baseado em configuração XML.

Programa de Teste de Configuração XML de Mapeamento Um para Um do Hibernate

No exemplo do programa de teste de mapeamento um para um do Hibernate, primeiro criaremos um objeto Txn e o salvaremos. Uma vez salvo no banco de dados, usaremos o id gerado para recuperar o objeto Txn e imprimi-lo.

package com.journaldev.hibernate.main;

import java.util.Date;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.journaldev.hibernate.model.Customer;
import com.journaldev.hibernate.model.Txn;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateOneToOneMain {

	public static void main(String[] args) {
		
		Txn txn = buildDemoTransaction();
		
		SessionFactory sessionFactory = null;
		Session session = null;
		Transaction tx = null;
		try{
		// Obter Session
		sessionFactory = HibernateUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created");
		// Iniciar transação
		tx = session.beginTransaction();
		// Salvar o objeto do Modelo
		session.save(txn);
		// Confirmar transação
		tx.commit();
		System.out.println("Transaction ID="+txn.getId());
		
		// Obter Dados da Transação Salva
		printTransactionData(txn.getId(), sessionFactory);
		
		}catch(Exception e){
			System.out.println("Exception occured. "+e.getMessage());
			e.printStackTrace();
		}finally{
			if(!sessionFactory.isClosed()){
				System.out.println("Closing SessionFactory");
				sessionFactory.close();
			}
		}
	}

	private static void printTransactionData(long id, SessionFactory sessionFactory) {
		Session session = null;
		Transaction tx = null;
		try{
			// Obter Session
			sessionFactory = HibernateUtil.getSessionFactory();
			session = sessionFactory.getCurrentSession();
			// Iniciar transação
			tx = session.beginTransaction();
			// Salvar o objeto do Modelo
			Txn txn = (Txn) session.get(Txn.class, id);
			// Confirmar transação
			tx.commit();
			System.out.println("Transaction Details=\n"+txn);
			
			}catch(Exception e){
				System.out.println("Exception occured. "+e.getMessage());
				e.printStackTrace();
			}
	}

	private static Txn buildDemoTransaction() {
		Txn txn = new Txn();
		txn.setDate(new Date());
		txn.setTotal(100);
		
		Customer cust = new Customer();
		cust.setAddress("Bangalore, India");
		cust.setEmail("[email protected]");
		cust.setName("Pankaj Kumar");
		
		txn.setCustomer(cust);
		
		cust.setTxn(txn);
		return txn;
	}

}

Agora, quando executamos o programa de teste de mapeamento um para um acima no Hibernate, obtemos a seguinte saída.

Hibernate Configuration loaded
Hibernate serviceRegistry created
Session created
Hibernate: insert into TRANSACTION (txn_date, txn_total) values (?, ?)
Hibernate: insert into CUSTOMER (cust_name, cust_email, cust_address, txn_id) values (?, ?, ?, ?)
Transaction ID=19
Hibernate: select txn0_.txn_id as txn_id1_1_0_, txn0_.txn_date as txn_date2_1_0_, txn0_.txn_total as txn_tota3_1_0_, 
customer1_.txn_id as txn_id1_0_1_, customer1_.cust_name as cust_nam2_0_1_, customer1_.cust_email as cust_ema3_0_1_, 
customer1_.cust_address as cust_add4_0_1_ from TRANSACTION txn0_ left outer join CUSTOMER customer1_ on 
txn0_.txn_id=customer1_.txn_id where txn0_.txn_id=?
Transaction Details=
19, 100.0, Pankaj Kumar, [email protected], Bangalore, India
Closing SessionFactory

Como você pode ver, está funcionando bem e conseguimos recuperar dados de ambas as tabelas usando o ID da transação. Verifique o SQL usado pelo Hibernate internamente para obter os dados, ele está usando joins para obter os dados de ambas as tabelas.

Mapeamento Um para Um do Hibernate com Anotação

Na seção acima, vimos como usar configuração baseada em XML para o mapeamento um para um do Hibernate. Agora, vamos ver como podemos usar JPA e anotações do Hibernate para alcançar a mesma coisa.

Arquivo de Configuração do Hibernate

hibernate-annotation.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.password">pankaj123</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
        <property name="hibernate.connection.username">pankaj</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <property name="hibernate.current_session_context_class">thread</property>
        <property name="hibernate.show_sql">true</property>
        
        <mapping class="com.journaldev.hibernate.model.Txn1"/>
        <mapping class="com.journaldev.hibernate.model.Customer1"/>
    </session-factory>
</hibernate-configuration>

A configuração do Hibernate é simples, como você pode ver que eu tenho duas classes de modelo que usaremos com anotações – Txn1 e Customer1.

Exemplo de Classes de Modelo de Mapeamento Um para Um do Hibernate com Anotação

Para a configuração de anotações de mapeamento um para um no Hibernate, as classes de modelo são a parte mais importante. Vamos ver como nossas classes de modelo se parecem.

package com.journaldev.hibernate.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

@Entity
@Table(name="TRANSACTION")
public class Txn1 {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="txn_id")
	private long id;
	
	@Column(name="txn_date")
	private Date date;
	
	@Column(name="txn_total")
	private double total;
	
	@OneToOne(mappedBy="txn")
	@Cascade(value=org.hibernate.annotations.CascadeType.SAVE_UPDATE)
	private Customer1 customer;
	
	@Override
	public String toString(){
		return id+", "+total+", "+customer.getName()+", "+customer.getEmail()+", "+customer.getAddress();
	}

        // Métodos Getter-Setter, omitidos para clareza 
}

Observe que a maioria das anotações são da API de Persistência do Java, pois o Hibernate fornece sua implementação. No entanto, para o cascata, precisaríamos usar a anotação do Hibernate org.hibernate.annotations.Cascade e a enumeração org.hibernate.annotations.CascadeType.

package com.journaldev.hibernate.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

@Entity
@Table(name="CUSTOMER")
public class Customer1 {

	@Id
	@Column(name="txn_id", unique=true, nullable=false)
	@GeneratedValue(generator="gen")
	@GenericGenerator(name="gen", strategy="foreign", parameters={@Parameter(name="property", value="txn")})
	private long id;
	
	@Column(name="cust_name")
	private String name;
	
	@Column(name="cust_email")
	private String email;
	
	@Column(name="cust_address")
	private String address;
	
	@OneToOne
	@PrimaryKeyJoinColumn
	private Txn1 txn;

        // Métodos Getter-Setter
}

Observe que precisaríamos de @GenericGenerator para que o ID seja usado a partir da transação, em vez de gerá-lo.

Classe Utilitária do SessionFactory do Hibernate

A criação da SessionFactory é independente da maneira como fornecemos o mapeamento do Hibernate. Nossa classe utilitária para criar a SessionFactory parece assim.

package com.journaldev.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateAnnotationUtil {

	private static SessionFactory sessionFactory;
	
	private static SessionFactory buildSessionFactory() {
        try {
            // Crie a SessionFactory a partir do hibernate-annotation.cfg.xml
        	Configuration configuration = new Configuration();
        	configuration.configure("hibernate-annotation.cfg.xml");
        	System.out.println("Hibernate Annotation Configuration loaded");
        	
        	ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        	System.out.println("Hibernate Annotation serviceRegistry created");
        	
        	SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        	
            return sessionFactory;
        }
        catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            ex.printStackTrace();
            throw new ExceptionInInitializerError(ex);
        }
    }
	
	public static SessionFactory getSessionFactory() {
		if(sessionFactory == null) sessionFactory = buildSessionFactory();
        return sessionFactory;
    }
}

Exemplo de Programa de Teste para Mapeamento Um para Um com Anotação no Hibernate

Aqui está um programa de teste simples para nosso exemplo de mapeamento um para um com anotação no Hibernate.

package com.journaldev.hibernate.main;

import java.util.Date;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.journaldev.hibernate.model.Customer1;
import com.journaldev.hibernate.model.Txn1;
import com.journaldev.hibernate.util.HibernateAnnotationUtil;

public class HibernateOneToOneAnnotationMain {

	public static void main(String[] args) {
		
		Txn1 txn = buildDemoTransaction();
		
		SessionFactory sessionFactory = null;
		Session session = null;
		Transaction tx = null;
		try{
		//Obter Sessão
		sessionFactory = HibernateAnnotationUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created using annotations configuration");
		//iniciar transação
		tx = session.beginTransaction();
		//Salvar o objeto Modelo
		session.save(txn);
		//Cometer transação
		tx.commit();
		System.out.println("Annotation Example. Transaction ID="+txn.getId());
		
		//Obter Dados de Transação Salvos
		printTransactionData(txn.getId(), sessionFactory);
		}catch(Exception e){
			System.out.println("Exception occured. "+e.getMessage());
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()){
				System.out.println("Closing SessionFactory");
				sessionFactory.close();
			}
		}
	}

	private static void printTransactionData(long id, SessionFactory sessionFactory) {
		Session session = null;
		Transaction tx = null;
		try{
			//Obter Sessão
			sessionFactory = HibernateAnnotationUtil.getSessionFactory();
			session = sessionFactory.getCurrentSession();
			//iniciar transação
			tx = session.beginTransaction();
			//Salvar o objeto Modelo
			Txn1 txn = (Txn1) session.get(Txn1.class, id);
			//Cometer transação
			tx.commit();
			System.out.println("Annotation Example. Transaction Details=\n"+txn);
			
			}catch(Exception e){
				System.out.println("Exception occured. "+e.getMessage());
				e.printStackTrace();
			}
	}

	private static Txn1 buildDemoTransaction() {
		Txn1 txn = new Txn1();
		txn.setDate(new Date());
		txn.setTotal(100);
		
		Customer1 cust = new Customer1();
		cust.setAddress("San Jose, USA");
		cust.setEmail("[email protected]");
		cust.setName("Pankaj Kr");
		
		txn.setCustomer(cust);
		
		cust.setTxn(txn);
		return txn;
	}

}

Aqui está o trecho de saída quando executamos o programa acima.

Hibernate Annotation Configuration loaded
Hibernate Annotation serviceRegistry created
Session created using annotations configuration
Hibernate: insert into TRANSACTION (txn_date, txn_total) values (?, ?)
Hibernate: insert into CUSTOMER (cust_address, cust_email, cust_name, txn_id) values (?, ?, ?, ?)
Annotation Example. Transaction ID=20
Hibernate: select txn1x0_.txn_id as txn_id1_1_0_, txn1x0_.txn_date as txn_date2_1_0_, txn1x0_.txn_total as txn_tota3_1_0_, 
customer1x1_.txn_id as txn_id1_0_1_, customer1x1_.cust_address as cust_add2_0_1_, customer1x1_.cust_email as cust_ema3_0_1_, 
customer1x1_.cust_name as cust_nam4_0_1_ from TRANSACTION txn1x0_ left outer join CUSTOMER customer1x1_ on 
txn1x0_.txn_id=customer1x1_.txn_id where txn1x0_.txn_id=?
Annotation Example. Transaction Details=
20, 100.0, Pankaj Kr, [email protected], San Jose, USA
Closing SessionFactory

Observe que a saída é semelhante à configuração baseada em XML de um para um do Hibernate. Isso é tudo para o exemplo de mapeamento um para um do Hibernate, você pode baixar o projeto final no link abaixo e brincar com ele para aprender mais.

Baixar Projeto de Mapeamento OneToOne do Hibernate

Source:
https://www.digitalocean.com/community/tutorials/hibernate-one-to-one-mapping-example-annotation