Hibernate 세션은 Java 응용 프로그램과 Hibernate 프레임워크 간의 인터페이스입니다. 오늘은 테이블에 데이터를 저장하고 업데이트하는 데 중요한 메서드들인 save, saveOrUpdate, persist, update, 및 merge에 대해 살펴보겠습니다.
Hibernate 세션
Hibernate 세션 save
메서드 이름에서 알 수 있듯이, hibernate save()는 엔터티를 데이터베이스에 저장하는 데 사용될 수 있습니다. 이 메서드를 트랜잭션 외부에서 호출할 수 있기 때문에 이 메서드를 데이터 저장에 사용하지 않는 편입니다. 트랜잭션이 없는 상태에서 이 메서드를 사용하고 엔터티 간에 cascading이 있는 경우에만 주 엔터티가 저장됩니다 세션을 flush하지 않는 한. 테스트 목적으로 두 개의 엔터티 빈인 Employee
와 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.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 메서드
@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 메서드
@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
*****
위의 출력에서 확인할 수 있는 몇 가지 중요한 점은 다음과 같습니다:
- 트랜잭션 경계 바깥에 저장하는 것을 피해야 합니다. 그렇지 않으면 매핑된 엔티티가 저장되지 않아 데이터 불일치가 발생할 수 있습니다. 세션을 플러시하는 것을 잊는 것은 매우 정상적입니다. 왜냐하면 예외나 경고가 발생하지 않기 때문입니다.
- 하이버네이트의 저장 메서드는 생성된 ID를 즉시 반환합니다. 이것은 저장 메서드가 호출될 때 기본 객체가 즉시 저장되기 때문에 가능합니다.
- 기본 객체에서 매핑된 다른 객체가 있는 경우에는 트랜잭션을 커밋하거나 세션을 플러시할 때 저장됩니다.
- 지속 상태에 있는 객체의 경우, 저장은 업데이트 쿼리를 통해 데이터를 업데이트합니다. 이것은 트랜잭션이 커밋될 때 발생합니다. 객체에 변경 사항이 없으면 쿼리가 발생하지 않습니다. 위의 프로그램을 여러 번 실행하면 열 값에 변경 사항이 없기 때문에 다음에는 업데이트 쿼리가 발생하지 않음을 알 수 있습니다.
- 하이버네이트 저장은 엔티티 객체를 지속적인 컨텍스트로 로드하며, 저장 호출 후에 트랜잭션이 커밋되기 전에 객체 속성을 업데이트하면 데이터베이스에 저장됩니다.
Hibernate Persist
하이버네이트 영속화는 저장 (트랜잭션 포함)과 유사하며 엔티티 객체를 영속 컨텍스트에 추가하여 이후 변경 사항을 추적합니다. 트랜잭션이 커밋되거나 세션이 플러시되기 전에 객체 속성이 변경되면 데이터베이스에도 저장됩니다. 두 번째 차이점은 persist()
메서드를 트랜잭션 경계 내에서만 사용할 수 있다는 것입니다. 따라서 안전하며 연관된 객체를 처리합니다. 마지막으로, persist는 아무것도 반환하지 않으므로 생성된 식별자 값을 얻으려면 영속화된 객체를 사용해야 합니다. 간단한 프로그램에서 하이버네이트 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()
를 사용할 수 있지만, 세션이 flush되지 않으면 매핑된 객체가 저장되지 않는 문제가 다시 발생할 것입니다. Hibernate saveOrUpdate는 엔티티 객체를 영속적인 컨텍스트에 추가하고 이후의 변경 사항을 추적합니다. 변경 사항은 트랜잭션을 커밋할 때 저장되며 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 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)만 저장되고 주소 정보가 손실됩니다. 트랜잭션에 직원 객체가 변경 사항을 추적하기 때문에 마지막 호출에서도 직원 테이블에 업데이트가 없습니다. 값이 변경되었지만 최종 값은 동일합니다.
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
값이 업데이트되지 않았음을 주목하세요. 값의 업데이트가 없기 때문에 처음 실행 후에는 업데이트가 발생하지 않습니다. 또한 “Final updated name”인 직원 이름을 확인하세요. 이는 update() 메서드를 호출한 후에 설정한 값입니다. 이는 하이버네이트가 객체를 변경 사항을 추적하고 트랜잭션을 커밋할 때 이 값이 저장된 것을 확인합니다.
Hibernate 병합
Hibernate 병합은 기존 값을 업데이트하는 데 사용될 수 있지만, 이 메서드는 전달된 엔티티 객체에서 복사본을 만들어 반환합니다. 반환된 객체는 영속적인 컨텍스트의 일부이며 변경 사항이 추적됩니다. 전달된 객체는 추적되지 않습니다. 이것이 다른 모든 메서드와의 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();
// 병합 예제 - 데이터가 이미 테이블에 존재합니다
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()에 의해 반환된 엔티티 객체가 전달된 엔티티와 다른 것임을 알아두세요. 또한 추가 실행에서 이름이 “Kumar”인 것에 유의하세요. 이는 반환된 객체가 변경 사항을 추적하기 때문입니다. 이것이 하이버네이트 세션 save
및 update
메서드에 대한 모든 것입니다. 위의 예제가 귀하의 의문을 명확히하는 데 도움이 되기를 바랍니다.