アノテーションを使用したHibernateの1対1マッピングの例

今日はHibernateでのOne to Oneマッピングを見ていきます。注釈とXML構成を使用したHibernate One To Oneマッピングの例を見ていきます。

HibernateでのOne to Oneマッピング

通常、データベースのテーブルは互いに関連付けられています。関連付けの形式にはいくつかの種類があり、one-to-oneone-to-manymany-to-manyなどがあります。これらはさらに単方向と双方向のマッピングに分類できます。今日は、Hibernate One to OneマッピングXML構成および注釈構成を使用して実装する方法を見ていきます。

Hibernate One to Oneマッピングの例のデータベース設定

まず、データベーステーブルで1対1のマッピングを設定する必要があります。この例では、TransactionとCustomerの2つのテーブルを作成します。これらのテーブルはどちらも1対1のマッピングを持ちます。Transactionは主テーブルとなり、Customerテーブルでは1対1のマッピングに外部キーを使用します。このチュートリアルで使用するデータベースはMySQLですが、他のデータベースを使用している場合は、スクリプトを適宜変更してください。

-- Transactionテーブルの作成
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;

-- Customerテーブルの作成
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;

上記のテーブル間の1対1のマッピングのエンティティ関係図(ERD)は、以下の画像のようになります。データベースの設定が完了したので、Hibernate One to One Example Projectに移動しましょう。

Hibernate One to One Mapping Example Projectの構造

シンプルなMavenプロジェクトをJava IDEで作成します。私はEclipseを使用しています。最終的なプロジェクト構造は以下の画像のようになります。まず、XMLベースのHibernate One to Oneマッピングの例を見てから、同じことをアノテーションを使用して実装します。

Hibernate Maven Dependencies

最終的なpom.xmlファイルは以下のようになります。

<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>

依存関係は、HibernateとMySQL Javaドライバーのみです。Hibernateの最新バージョン4.3.5.FinalおよびMySQLデータベースサーバーバージョン(5.0.5)に基づいてMySQL Javaドライバーを使用していることに注意してください。Hibernate 4はJBossログを使用し、これはトランジティブな依存関係として自動的にインポートされます。これはプロジェクトのMaven依存関係で確認できます。古いバージョンのHibernateを使用している場合は、slf4j依存関係を追加する必要があるかもしれません。

Hibernate One to One Mapping Model Classes

データベーステーブルを反映するためのHibernate One to Oneマッピングのモデルクラスは次のようになります。

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;
	}
	
}

XMLベースのマッピングには、上記のモデルクラスがシンプルなPOJOクラスまたはgetter-setterメソッドを持つJava Beansであることに注意しています。混乱を避けるために、クラス名をTxnとしています。Hibernate APIではクラス名がTransactionとなっています。

Hibernate One to One Mapping Configuration

TxnとCustomerテーブルのハイバネートの1対1のマッピング構成ファイルを作成しましょう。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>

上記の重要なポイントは、customerプロパティのハイバネートone-to-one要素です。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>

generator class=“foreign”は、ハイバネート外部キーの実装に使用される重要な部分です。

Hibernate Configuration File

XMLベースのHibernateマッピング構成のHibernate構成ファイルは次のとおりです。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>

ハイバネート構成ファイルはシンプルであり、データベース接続プロパティとハイバネートマッピングリソースを持っています。

Hibernate SessionFactory ユーティリティ

こちらは、Hibernate SessionFactory インスタンスを作成するためのユーティリティクラスです。

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 {
            // hibernate.cfg.xml から SessionFactory を作成します。
        	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;
    }
}

これで、Hibernate の one to one マッピング XML ベースの構成をテストするテストプログラムを書きましょう。

Hibernate One to One マッピング XML 構成テストプログラム

一対一のマッピング例のテストプログラムでは、まず Txn オブジェクトを作成して保存します。 それをデータベースに保存した後、生成された ID を使用して Txn オブジェクトを取得して印刷します。

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{
		// セッションを取得します
		sessionFactory = HibernateUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created");
		// トランザクションを開始します
		tx = session.beginTransaction();
		// モデルオブジェクトを保存します
		session.save(txn);
		// トランザクションをコミットします
		tx.commit();
		System.out.println("Transaction ID="+txn.getId());
		
		// 保存されたトランザクションデータを取得します
		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{
			// セッションを取得します
			sessionFactory = HibernateUtil.getSessionFactory();
			session = sessionFactory.getCurrentSession();
			// トランザクションを開始します
			tx = session.beginTransaction();
			// モデルオブジェクトを保存します
			Txn txn = (Txn) session.get(Txn.class, id);
			// トランザクションをコミットします
			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;
	}

}

以上が、Hibernate テストプログラムでの one to one マッピングを実行すると、次の出力が得られます。

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

ご覧の通り、正常に機能しており、トランザクションIDを使用して両方のテーブルからデータを取得できています。データを取得するためにHibernateが内部で使用しているSQLを確認してください。両方のテーブルからデータを取得するために結合を使用しています。

Hibernate One to Oneマッピングのアノテーション

上記のセクションでは、Hibernate One to OneマッピングのためにXMLベースの設定を使用する方法を見ましたが、今度はJPAとHibernateのアノテーションを使用して同じことを達成する方法を見てみましょう。

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>

Hibernateの設定はシンプルです。アノテーションを使用するために使用する2つのモデルクラスがあります- Txn1Customer1

Hibernate One to Oneマッピングのアノテーションの例モデルクラス

Hibernateの1対1マッピングアノテーション構成では、モデルクラスが最も重要な部分です。モデルクラスを見てみましょう。

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();
	}

        // クラリティのためにGetter-Setterメソッドは省略されています 
}

注釈のほとんどはJava Persistence APIから来ていますが、Hibernateはその実装を提供しています。ただし、カスケードに関しては、Hibernateアノテーションorg.hibernate.annotations.Cascadeと列挙型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;

        // Getter-Setterメソッド
}

idを生成するのではなく、txnから使用するために@GenericGeneratorが必要です。

Hibernate SessionFactoryユーティリティクラス

SessionFactoryの作成は、Hibernateマッピングを提供する方法とは独立しています。SessionFactoryを作成するためのユーティリティクラスは以下のようになります。

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 {
            // hibernate-annotation.cfg.xmlからSessionFactoryを作成
        	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;
    }
}

Hibernate One to One Mapping Annotation Example Test Program

こちらは、Hibernateの1対1マッピングアノテーションの例のテストプログラムです。

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{
		//セッションを取得
		sessionFactory = HibernateAnnotationUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created using annotations configuration");
		//トランザクションを開始
		tx = session.beginTransaction();
		//モデルオブジェクトを保存
		session.save(txn);
		//トランザクションをコミット
		tx.commit();
		System.out.println("Annotation Example. Transaction ID="+txn.getId());
		
		//保存されたトランザクションデータを取得
		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{
			//セッションを取得
			sessionFactory = HibernateAnnotationUtil.getSessionFactory();
			session = sessionFactory.getCurrentSession();
			//トランザクションを開始
			tx = session.beginTransaction();
			//モデルオブジェクトを保存
			Txn1 txn = (Txn1) session.get(Txn1.class, id);
			//トランザクションをコミット
			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;
	}

}

上記のプログラムを実行すると、以下は出力スニペットです。

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

出力はハイバネートのワン・トゥ・ワンXMLベースの構成に類似していることに注意してください。これでHibernate One to Oneマッピングの例は終わりです。最終プロジェクトは以下のリンクからダウンロードして、詳細を学ぶために試してみてください。

Hibernate OneToOne Mappingプロジェクトのダウンロード

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