Session là cầu nối giữa ứng dụng Java và Hibernate framework. Trong bài viết này, chúng ta sẽ tìm hiểu các phương thức quan trọng của Hibernate Session dùng để lưu và cập nhật dữ liệu trong bảng: save, saveOrUpdate, persist, update và merge.
Phương thức của Hibernate Session
save
Đúng như tên gọi của nó, hibernate save() được dùng để lưu một entity vào cơ sở dữ liệu. Ta có thể gọi phương thức này bên ngoài một transaction, và đây cũng là một điểm yếu khi dùng nó để lưu dữ liệu. Nếu sử dụng save()
không có transaction và có mối quan hệ cascading giữa các entity (thao tác trên entity cha tự động áp dụng cho entity con liên quan), thì chỉ có entity chính được lưu, trừ khi ta flush (xóa hết dữ liệu) session hiện tại.
Để thử nghiệm, ta sẽ tạo hai entity bean là Employee
and 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 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;
}
}
Dưới đây là một chương trình hibernate đơn giản, trong đó ta gọi phương thức save()
trong các trường hợp khác nhau.
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) {
// Prep Work
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
//save example - without transaction
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("*****");
//save example - with transaction
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("*****");
//save example - existing row in table
Session session6 = sessionFactory.openSession();
Transaction tx6 = session6.beginTransaction();
Employee emp6 = (Employee) session6.load(Employee.class, new Long(20));
//update some data
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("*****");
// Close resources
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;
}
}
Khi chạy chương trình trên, nó sẽ cho ra kết quả như sau.
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
*****
Từ kết quả trên, ta có thể rút ra một vài điểm quan trọng:
- Ta nên tránh dùng
save()
bên ngoài một transaction, nếu không các entity được map sẽ không được lưu, dẫn đến dữ liệu không nhất quán. Việc quên flush session rất dễ xảy ra vì nó không đưa ra bất kỳ exception hay cảnh báo nào. - Phương thức
save()
của Hibernate ngay lập tức trả về ID được tạo ra, nhờ vào việc đối tượng chính đã được lưu ngay khisave()
được gọi. - Nếu có các đối tượng khác được map từ đối tượng chính, chúng sẽ được lưu tại thời điểm tạo transaction hoặc khi ta flush session.
- Đối với các đối tượng đang ở trạng thái persistent (lưu trữ lâu dài),
save()
sẽ cập nhật dữ liệu thông qua câu lệnhupdate
. Lưu ý rằng việc này chỉ xảy ra khi transaction được tạo. Nếu đối tượng không có thay đổi nào, sẽ không có câu lệnh nào được thực thi. Nếu bạn chạy chương trình trên nhiều lần, bạn sẽ thấy rằng các câu lệnhupdate
không được kích hoạt ở những lần sau vì không có sự thay đổi trong giá trị các cột. - Phương thức
save()
của Hibernate đưa đối tượng entity vào persistent context (context lưu trữ lâu dài). Nếu bạn cập nhật các thuộc tính của đối tượng sau lệnh gọisave()
nhưng trước khi transaction được tạo, thay đổi đó cũng sẽ được lưu vào cơ sở dữ liệu.
persist
persist()
tương tự như save()
(khi được dùng trong transaction) và cũng thêm đối tượng entity vào persistent context, do đó mọi thay đổi sau đó đều được theo dõi. Nếu các thuộc tính của đối tượng thay đổi trước khi transaction được tạo hoặc session được flush, thay đổi đó cũng sẽ được lưu vào cơ sở dữ liệu.
Điểm khác biệt thứ hai là ta chỉ có thể dùng persist()
trong phạm vi của một transaction, vì vậy nó an toàn hơn và xử lý luôn các đối tượng cascading. Cuối cùng, persist()
không trả về giá trị gì, nên ta cần dùng chính đối tượng đã được persist để lấy giá trị định danh (ID) được tạo ra.
Hãy xem cách dùng persist()
với một chương trình đơn giản.
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) {
// Prep Work
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
//persist example - with transaction
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("*****");
// Close resources
sessionFactory.close();
}
}
Kết quả được tạo ra bởi đoạn mã trên là:
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=?
*****
Lưu ý rằng đối tượng employee thứ nhất được chèn vào, sau đó tại thời điểm tạo transaction, câu lệnh update
được thực thi để cập nhật giá trị name
. Đối tượng address đã được map cũng được lưu vào cơ sở dữ liệu.
saveOrUpdate
saveOrUpdate()
sẽ thực thi câu lệnh insert
hoặc update
tùy thuộc vào dữ liệu được cung cấp. Nếu dữ liệu đã tồn tại trong cơ sở dữ liệu, nó sẽ chạy update
. Ta cũng có thể sử dụng saveOrUpdate()
mà không cần transaction. Nhưng giống như trước, bạn sẽ gặp vấn đề với các đối tượng được map không được lưu nếu session không được flush.
saveOrUpdate()
thêm đối tượng entity vào persistent context và theo dõi mọi thay đổi sau đó. Tương tự như persist()
, mọi thay đổi tiếp theo sẽ được lưu tại thời điểm tạo transaction.
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) {
// Prep Work
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
//saveOrUpdate example - without transaction
Session session5 = sessionFactory.openSession();
Employee emp5 = HibernateSaveExample.getTestEmployee();
session5.saveOrUpdate(emp5);
System.out.println("*****");
//saveOrUpdate example - with transaction
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("*****");
// Close resources
sessionFactory.close();
}
}
Chương trình trên cho ra kết quả sau.
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
*****
Lưu ý rằng khi không có transaction, chỉ có Employee được lưu và thông tin address bị mất. Với transaction, mọi thay đổi của đối tượng employee sẽ được theo dõi, Đó là lý do tại sao trong lệnh gọi cuối cùng không có update
nào trong bảng Employee mặc dù giá trị đã được thay đổi, vì giá trị cuối cùng vẫn không đổi.
update
update()
nên được dùng khi ta biết chắc rằng mình chỉ cập nhật thông tin cho entity. Thao tác này thêm đối tượng entity vào persistent context, và các thay đổi sau đó sẽ được theo dõi và lưu lại khi transaction được tạo.
Hãy kiểm tra hành vi này với một chương trình đơn giản.
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) {
// Prep Work
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();
// update example
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");
// Close resources
sessionFactory.close();
}
}
Khi ta chạy chương trình trên lần đầu tiên, ta nhận được kết quả sau.
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
Ở những lần sau, ta nhận được kết quả sau.
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
Lưu ý rằng không có câu lệnh update()
nào được kích hoạt sau lần chạy đầu tiên vì không có sự thay đổi nào trong giá trị. Cũng lưu ý rằng tên của employee là “Final updated name”. Ta đã đặt nó sau khi gọi phương thức update()
. Điều này xác nhận rằng Hibernate đã theo dõi mọi thay đổi của đối tượng và tại thời điểm tạo transaction, giá trị này đã được lưu lại.
merge
merge()
có thể được dùng để cập nhật các giá trị hiện có. Tuy nhiên, phương thức này tạo ra một bản sao từ đối tượng entity được truyền vào và trả về bản sao đó. Đối tượng được trả về là một phần của persistent context và được theo dõi với mọi thay đổi, còn đối tượng ban đầu được truyền vào thì không. Đây là điểm khác biệt lớn nhất của merge()
so với tất cả các phương thức khác.
Hãy xem cách nó hoạt động với một chương trình đơn giản.
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) {
// Prep Work
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 example - data already present in tables
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");
// Close resources
sessionFactory.close();
}
}
Kết quả trong lần chạy đầu tiên là:
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
Trong những lần tiếp theo, kết quả được tạo ra là:
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
Lưu ý rằng đối tượng entity được merge()
trả về khác với đối tượng entity được truyền vào. Cũng lưu ý rằng trong các lần chạy tiếp theo, tên ở trên sẽ là “Kumar”, vì đối tượng được trả về đang được theo dõi cho bất kỳ thay đổi nào.
Tổng kết
Trên đây là cơ chế hoạt động của phương thức của Hibernate Session. Hy vọng rằng các ví dụ trên đã giúp bạn giải đáp những thắc mắc bạn có, đặc biệt là về những khác biệt cơ bản giữa chúng.