Hibernate Session merge、update、save、saveOrUpdate、persistの例

Hibernateセッションは、JavaアプリケーションとHibernateフレームワークの間のインタフェースです。今日は、テーブルにデータを保存および更新するためのセッションの重要なメソッドであるsavesaveOrUpdatepersistupdate、およびmergeについて説明します。

Hibernateセッション

Hibernateセッションの保存

メソッド名が示すように、hibernate save()はエンティティをデータベースに保存するために使用されます。このメソッドはトランザクションの外部から呼び出すことができるため、データの保存にはこのメソッドを使用することは好ましくありません。トランザクションなしで使用し、エンティティ間にカスケードがある場合、主エンティティのみが保存されますセッションをフラッシュしない限り。テスト目的のために、2つのエンティティビーン、EmployeeAddressがあります。

package com.journaldev.hibernate.model;

import javax.persistence.Access;
import javax.persistence.AccessType;
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 = "EMPLOYEE")
@Access(value=AccessType.FIELD)
public class Employee {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "emp_id")
	private long id;

	@Column(name = "emp_name")
	private String name;

	@Column(name = "emp_salary")
	private double salary;

	@OneToOne(mappedBy = "employee")
	@Cascade(value = org.hibernate.annotations.CascadeType.ALL)
	private Address address;

        //Getter setter methods

	@Override
	public String toString() {
		return "Id= " + id + ", Name= " + name + ", Salary= " + salary
				+ ", {Address= " + address + "}";
	}

}
package com.journaldev.hibernate.model;

import javax.persistence.Access;
import javax.persistence.AccessType;
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 = "ADDRESS")
@Access(value=AccessType.FIELD)
public class Address {

	@Id
	@Column(name = "emp_id", unique = true, nullable = false)
	@GeneratedValue(generator = "gen")
	@GenericGenerator(name = "gen", strategy = "foreign", parameters = { @Parameter(name = "property", value = "employee") })
	private long id;

	@Column(name = "address_line1")
	private String addressLine1;

	@Column(name = "zipcode")
	private String zipcode;

	@Column(name = "city")
	private String city;

	@OneToOne
	@PrimaryKeyJoinColumn
	private Employee employee;

        //Getter setter methods

	@Override
	public String toString() {
		return "AddressLine1= " + addressLine1 + ", City=" + city
				+ ", Zipcode=" + zipcode;
	}
}

以下は、save()メソッドを異なるケースで呼び出すシンプルなHibernateプログラムです。

package com.journaldev.hibernate.main;

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

import com.journaldev.hibernate.model.Address;
import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateSaveExample {

	public static void main(String[] args) {
		
		// 準備作業
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		
		// トランザクションなしの保存の例
		Session session = sessionFactory.openSession();
		Employee emp = getTestEmployee();
		long id = (Long) session.save(emp);
		System.out.println("1. Employee save called without transaction, id="+id);
		session.flush(); //address will not get saved without this
		System.out.println("*****");
		
		// トランザクションありの保存の例
		Transaction tx1 = session.beginTransaction();
		Session session1 = sessionFactory.openSession();
		Employee emp1 = getTestEmployee();
		long id1 = (Long) session1.save(emp1);
		System.out.println("2. Employee save called with transaction, id="+id1);
		System.out.println("3. Before committing save transaction");
		tx1.commit();
		System.out.println("4. After committing save transaction");
		System.out.println("*****");
		
		// テーブル内の既存の行の保存の例
		Session session6 = sessionFactory.openSession();
		Transaction tx6 = session6.beginTransaction();
		Employee emp6 =  (Employee) session6.load(Employee.class, new Long(20));
		
		// データの一部を更新する
		System.out.println("Employee Details="+emp6);
		emp6.setName("New Name");
		emp6.getAddress().setCity("New City");
		
		long id6 = (Long) session6.save(emp6);
		emp6.setName("New Name1"); // will get updated in database
		System.out.println("5. Employee save called with transaction, id="+id6);
		System.out.println("6. Before committing save transaction");
		tx6.commit();
		System.out.println("7. After committing save transaction");
		System.out.println("*****");
		
		// リソースを閉じる
		sessionFactory.close();

	}

	public static Employee getTestEmployee() {
		Employee emp = new Employee();
		Address add = new Address();
		emp.setName("Test Emp");
		emp.setSalary(1000);
		add.setAddressLine1("Test address1");
		add.setCity("Test City");
		add.setZipcode("12121");
		emp.setAddress(add);
		add.setEmployee(emp);
		return emp;
	}
}

上記のプログラムを実行すると、以下の出力が生成されます。

Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
1. Employee save called without transaction, id=149
Hibernate: insert into ADDRESS (address_line1, city, zipcode, emp_id) values (?, ?, ?, ?)
*****
Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
2. Employee save called with transaction, id=150
3. Before committing save transaction
Hibernate: insert into ADDRESS (address_line1, city, zipcode, emp_id) values (?, ?, ?, ?)
4. After committing save transaction
*****
Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
Employee Details=Id= 20, Name= Kumar1, Salary= 1000.0, {Address= AddressLine1= Test address1, City=Blr, Zipcode=12121}
5. Employee save called with transaction, id=20
6. Before committing save transaction
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
Hibernate: update ADDRESS set address_line1=?, city=?, zipcode=? where emp_id=?
7. After committing save transaction
*****

上記の出力から確認できるいくつかの重要なポイントは次のとおりです:

  • トランザクションの範囲外での保存は避けるべきです。そうしないと、マッピングされたエンティティが保存されず、データの不整合が発生する可能性があります。セッションのフラッシュを忘れることは非常に普通ですが、例外や警告はスローされません。
  • Hibernateのsaveメソッドは、生成されたIDをすぐに返します。これは、saveメソッドが呼び出されるとすぐに主キーオブジェクトが保存されるためです。
  • 主キーオブジェクトからマッピングされた他のオブジェクトがある場合、それらはトランザクションのコミット時またはセッションをフラッシュするときに保存されます。
  • 永続状態にあるオブジェクトに対して、saveは更新クエリを通じてデータを更新します。トランザクションがコミットされた時に行われることに注意してください。オブジェクトに変更がない場合、クエリは発行されません。上記のプログラムを複数回実行すると、次回は更新クエリが発行されないことがわかります。これは、列の値に変更がないためです。
  • Hibernateのsaveメソッドは、エンティティオブジェクトを永続的なコンテキストに読み込みます。save呼び出し後でトランザクションがコミットされる前にオブジェクトのプロパティを更新すると、データベースに保存されます。

Hibernate Persist

Hibernate persistは、トランザクションを伴うsaveと似ており、エンティティオブジェクトを永続化コンテキストに追加します。そのため、以降の変更は追跡されます。トランザクションがコミットされる前またはセッションがフラッシュされる前にオブジェクトのプロパティが変更された場合、データベースにも保存されます。2番目の違いは、persist()メソッドはトランザクションの境界内でのみ使用でき、安全で関連オブジェクトを管理します。最後に、persistは何も返さないため、生成された識別子の値を取得するために永続化されたオブジェクトを使用する必要があります。シンプルなプログラムでHibernateのpersistを見てみましょう。

package com.journaldev.hibernate.main;

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

import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernatePersistExample {

	public static void main(String[] args) {
		
		// 準備作業
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();	
		
		// persistの例 - トランザクションを伴う
		Session session2 = sessionFactory.openSession();
		Transaction tx2 = session2.beginTransaction();
		Employee emp2 = HibernateSaveExample.getTestEmployee();
		session2.persist(emp2);
		System.out.println("Persist called");
		emp2.setName("Kumar"); // will be updated in database too
		System.out.println("Employee Name updated");
		System.out.println("8. Employee persist called with transaction, id="+emp2.getId()+", address id="+emp2.getAddress().getId());
		tx2.commit();
		System.out.println("*****");
		
		// リソースをクローズする
		sessionFactory.close();

	}

}

上記のコードによって生成される出力は次の通りです:

Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
8. Employee persist called with transaction, id=158, address id=158
Hibernate: insert into ADDRESS (address_line1, city, zipcode, emp_id) values (?, ?, ?, ?)
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
*****

最初の従業員オブジェクトが挿入され、トランザクションのコミット時に名前の値を更新するクエリが実行されます。また、マップされたオブジェクトのアドレスもデータベースに保存されます。

Hibernate saveOrUpdate

HibernateのsaveOrUpdateは、提供されたデータに基づいて挿入または更新クエリを実行します。データがデータベースに存在する場合、更新クエリが実行されます。トランザクションなしでもsaveOrUpdate()を使用することができますが、セッションがフラッシュされない場合にマップされたオブジェクトが保存されないという問題が発生します。HibernateのsaveOrUpdateはエンティティオブジェクトを永続化コンテキストに追加し、さらなる変更を追跡します。さらなる変更はトランザクションのコミット時に保存されます。

package com.journaldev.hibernate.main;

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

import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateSaveOrUpdateExample {

	public static void main(String[] args) {
		
		// プレップワーク
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		
		// トランザクションなしのsaveOrUpdateの例
		Session session5 = sessionFactory.openSession();
		Employee emp5 = HibernateSaveExample.getTestEmployee();
		session5.saveOrUpdate(emp5);
		System.out.println("*****");
		
		// トランザクションありのsaveOrUpdateの例
		Session session3 = sessionFactory.openSession();
		Transaction tx3 = session3.beginTransaction();
		Employee emp3 = HibernateSaveExample.getTestEmployee();
		session3.saveOrUpdate(emp3);
		emp3.setName("Kumar"); //will be saved into DB
		System.out.println("9. Before committing saveOrUpdate transaction. Id="+emp3.getId());
		tx3.commit();
		System.out.println("10. After committing saveOrUpdate transaction");
		System.out.println("*****");
		
		
		Transaction tx4 = session3.beginTransaction();
		emp3.setName("Updated Test Name"); //Name changed
		emp3.getAddress().setCity("Updated City");
		session3.saveOrUpdate(emp3);
		emp3.setName("Kumar"); //again changed to previous value, so no Employee update
		System.out.println("11. Before committing saveOrUpdate transaction. Id="+emp3.getId());
		tx4.commit();
		System.out.println("12. After committing saveOrUpdate transaction");
		System.out.println("*****");

		// リソースをクローズ
		sessionFactory.close();

	}
}

上記のプログラムは以下の出力を生成します。

Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
*****
Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
9. Before committing saveOrUpdate transaction. Id=166
Hibernate: insert into ADDRESS (address_line1, city, zipcode, emp_id) values (?, ?, ?, ?)
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
10. After committing saveOrUpdate transaction
*****
11. Before committing saveOrUpdate transaction. Id=166
Hibernate: update ADDRESS set address_line1=?, city=?, zipcode=? where emp_id=?
12. After committing saveOrUpdate transaction
*****

トランザクションなしの場合、Employeeだけが保存され、アドレス情報は失われます。トランザクションでは、Employeeオブジェクトは変更を追跡するため、最後の呼び出しではEmployeeテーブルに更新がないことに注意してください。値が変更されたにもかかわらず、最終的な値は変わりません。

Hibernateのupdate

Hibernateのupdateは、エンティティ情報の更新のみ行う場合に使用する必要があります。この操作はエンティティオブジェクトを永続化コンテキストに追加し、さらなる変更はトランザクションがコミットされるときに追跡されて保存されます。単純なプログラムでこの動作を確認してみましょう。

package com.journaldev.hibernate.main;

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

import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateUpdateExample {

	public static void main(String[] args) {

		// 準備作業
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.openSession();
		Transaction tx = session.beginTransaction();
		Employee emp = (Employee) session.load(Employee.class, new Long(101));
		System.out.println("Employee object loaded. " + emp);
		tx.commit();

		// 更新の例
		emp.setName("Updated name");
		emp.getAddress().setCity("Bangalore");
		Transaction tx7 = session.beginTransaction();
		session.update(emp);
		emp.setName("Final updated name");
		System.out.println("13. Before committing update transaction");
		tx7.commit();
		System.out.println("14. After committing update transaction");

		// リソースを閉じる
		sessionFactory.close();

	}

}

上記のプログラムを初めて実行すると、以下の出力が得られます。

Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
Employee object loaded. Id= 101, Name= Test Emp, Salary= 1000.0, {Address= AddressLine1= Test address1, City=Test City, Zipcode=12121}
13. Before committing update transaction
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
Hibernate: update ADDRESS set address_line1=?, city=?, zipcode=? where emp_id=?
14. After committing update transaction

さらなる実行では、以下の出力が得られます。

Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
Employee object loaded. Id= 101, Name= Final updated name, Salary= 1000.0, {Address= AddressLine1= Test address1, City=Bangalore, Zipcode=12121}
13. Before committing update transaction
14. After committing update transaction

最初の実行後に更新が行われないことに注意してください。なぜなら、値に更新がないからです。また、”employee name”は、update()メソッドを呼び出した後に設定した”Final updated name”です。これにより、Hibernateはオブジェクトの変更をトラッキングし、トランザクションのコミット時にこの値が保存されることが確認されます。

Hibernate Merge

既存の値を更新するために、Hibernate mergeを使用することができます。ただし、このメソッドは渡されたエンティティオブジェクトからコピーを作成し、それを返します。返されたオブジェクトは永続コンテキストの一部であり、変更がトラッキングされますが、渡されたオブジェクトはトラッキングされません。これは、merge()が他のすべてのメソッドとの主な違いです。簡単なプログラムでこれを見てみましょう。

package com.journaldev.hibernate.main;

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

import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateMergeExample {

	public static void main(String[] args) {

		// 準備作業
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.openSession();
		Transaction tx = session.beginTransaction();
		Employee emp = (Employee) session.load(Employee.class, new Long(101));
		System.out.println("Employee object loaded. " + emp);
		tx.commit();

		 // mergeの例 - データは既にテーブルに存在しています
		 emp.setSalary(25000);
		 Transaction tx8 = session.beginTransaction();
		 Employee emp4 = (Employee) session.merge(emp);
		 System.out.println(emp4 == emp); // returns false
		 emp.setName("Test");
		 emp4.setName("Kumar");
		 System.out.println("15. Before committing merge transaction");
		 tx8.commit();
		 System.out.println("16. After committing merge transaction");

		// リソースを閉じる
		sessionFactory.close();

	}

}

最初の実行の出力は次のようになります。

Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
Employee object loaded. Id= 101, Name= Final updated name, Salary= 1000.0, {Address= AddressLine1= Test address1, City=Bangalore, Zipcode=12121}
false
15. Before committing merge transaction
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
16. After committing merge transaction

さらなる実行では、以下の出力が生成されます。

Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
Employee object loaded. Id= 101, Name= Kumar, Salary= 25000.0, {Address= AddressLine1= Test address1, City=Bangalore, Zipcode=12121}
false
15. Before committing merge transaction
16. After committing merge transaction

merge()で返されるエンティティオブジェクトは、渡されたエンティティと異なることに注意してください。また、さらなる実行では、nameが「Kumar」になっていることにも注意してください。これは、返されたオブジェクトが変更を追跡されているためです。これでHibernateセッションsaveメソッドとupdateメソッドに関する説明は終わりです。上記の例が疑問を明確にするのに役立つことを願っています。

Source:
https://www.digitalocean.com/community/tutorials/hibernate-session-merge-vs-update-save-saveorupdate-persist-example