Hibernate会话操作完全指南:合并、更新、保存与持久化方法实例解析

今天我们将研究Hibernate Session这个接口,它在Java应用程序和Hibernate框架之间起到了关键作用。我们将重点介绍Session中用于保存和更新表数据的重要方法,包括save、saveOrUpdate、persist、update和merge。

Hibernate 会话

Hibernate 会话保存

正如方法名所暗示的,Hibernate save() 方法可用于将实体保存到数据库。我们可以在事务之外调用此方法,这就是为什么我不推荐使用此方法保存数据的原因。如果我们在没有事务的情况下使用它并且实体之间存在级联关系,那么只有主要实体会被保存,除非我们刷新会话。为了测试目的,我们有两个实体bean – 员工和地址。

package com.Olivia.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.Olivia.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;
	}
}

以下是一个简单的Hibernate程序,演示了在不同情况下调用save()方法。

package com.Olivia.hibernate.main;

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

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

public class HibernateSaveExample {

	public static void main(String[] args) {
		
		// 准备工作
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		
		//save示例 - 无事务
		Session session = sessionFactory.openSession();
		Employee emp = getTestEmployee();
		long id = (Long) session.save(emp);
		System.out.println("1. 在无事务情况下调用Employee save方法, id="+id);
		session.flush(); //没有此操作address将不会被保存
		System.out.println("*****");
		
		//save示例 - 有事务
		Transaction tx1 = session.beginTransaction();
		Session session1 = sessionFactory.openSession();
		Employee emp1 = getTestEmployee();
		long id1 = (Long) session1.save(emp1);
		System.out.println("2. 在有事务情况下调用Employee save方法, id="+id1);
		System.out.println("3. 提交save事务前");
		tx1.commit();
		System.out.println("4. 提交save事务后");
		System.out.println("*****");
		
		//save示例 - 表中已存在的行
		Session session6 = sessionFactory.openSession();
		Transaction tx6 = session6.beginTransaction();
		Employee emp6 =  (Employee) session6.load(Employee.class, new Long(20));
		
		//更新一些数据
		System.out.println("员工详情="+emp6);
		emp6.setName("新名称");
		emp6.getAddress().setCity("新城市");
		
		long id6 = (Long) session6.save(emp6);
		emp6.setName("新名称1"); // 将在数据库中更新
		System.out.println("5. 在有事务情况下调用Employee save方法, id="+id6);
		System.out.println("6. 提交save事务前");
		tx6.commit();
		System.out.println("7. 提交save事务后");
		System.out.println("*****");
		
		// 关闭资源
		sessionFactory.close();

	}

	public static Employee getTestEmployee() {
		Employee emp = new Employee();
		Address add = new Address();
		emp.setName("测试员工");
		emp.setSalary(1000);
		add.setAddressLine1("测试地址1");
		add.setCity("测试城市");
		add.setZipcode("12121");
		emp.setAddress(add);
		add.setEmployee(emp);
		return emp;
	}
}

当我们执行以上程序时,会产生以下输出结果。

Hibernate: insert into EMPLOYEE (emp_name, emp_salary) values (?, ?)
1. 在无事务情况下调用Employee save方法, 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方法, id=150
3. 提交save事务前
Hibernate: insert into ADDRESS (address_line1, city, zipcode, emp_id) values (?, ?, ?, ?)
4. 提交save事务后
*****
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=?
员工详情=Id= 20, Name= Kumar1, Salary= 1000.0, {Address= AddressLine1= 测试地址1, City=Blr, Zipcode=12121}
5. 在有事务情况下调用Employee save方法, id=20
6. 提交save事务前
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
Hibernate: update ADDRESS set address_line1=?, city=?, zipcode=? where emp_id=?
7. 提交save事务后
*****

从上述输出中,我们可以确认以下几点:

  • 我们应该避免在事务边界外使用save方法,否则映射的实体将不会被保存,导致数据不一致。由于不会抛出任何异常或警告,很容易忘记刷新会话。
  • Hibernate save方法会立即返回生成的ID,这是因为主对象在调用save方法时就被立即保存了。
  • 如果存在从主对象映射的其他对象,它们会在提交事务或刷新会话时被保存。
  • 对于处于持久状态的对象,save方法通过更新查询来更新数据。请注意,这发生在事务提交时。如果对象没有发生变化,则不会触发任何查询。如果多次运行上述程序,会发现下次不会触发更新查询,因为列值没有变化。
  • Hibernate save方法将实体对象加载到持久化上下文中,如果在调用save方法之后但在事务提交之前更新对象属性,这些更改将被保存到数据库中。

Hibernate持久化(Persist)

Hibernate的persist()方法

Hibernate的persist()方法与save()方法(有事务)相似,它将实体对象添加到持久化上下文中,以便跟踪任何进一步的更改。如果在事务提交或会话刷新之前更改了对象的属性,这些更改也会保存到数据库中。第二个区别是我们只能在事务范围内使用persist()方法,因此它是安全的,并且会处理任何级联对象。最后,persist()方法不返回任何结果,所以我们需要使用持久化的对象来获取生成的标识符值。让我们通过一个简单的程序来了解Hibernate的persist()方法。

package com.Olivia.hibernate.main;

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

import com.Olivia.hibernate.model.Employee;
import com.Olivia.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方法已调用");
		emp2.setName("Kumar"); // 也会在数据库中更新
		System.out.println("员工姓名已更新");
		System.out.println("8. 使用事务调用persist方法,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方法将实体对象添加到持久化上下文中并跟踪任何进一步的更改。任何进一步的更改将在提交事务时保存,类似于persist方法。

package com.Olivia.hibernate.main;

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

import com.Olivia.hibernate.model.Employee;
import com.Olivia.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"); // 将保存到数据库
		System.out.println("9. 提交saveOrUpdate事务之前。Id="+emp3.getId());
		tx3.commit();
		System.out.println("10. 提交saveOrUpdate事务之后");
		System.out.println("*****");
		
		
		Transaction tx4 = session3.beginTransaction();
		emp3.setName("Updated Test Name"); // 姓名已更改
		emp3.getAddress().setCity("Updated City");
		session3.saveOrUpdate(emp3);
		emp3.setName("Kumar"); // 再次更改为之前的值,所以不会更新Employee
		System.out.println("11. 提交saveOrUpdate事务之前。Id="+emp3.getId());
		tx4.commit();
		System.out.println("12. 提交saveOrUpdate事务之后");
		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
*****

请注意,如果没有事务,只有员工信息会被保存,地址信息将丢失。有了事务,员工对象的变化将被追踪,这就是为什么在最后一个调用中,即使在期间值发生了更改,员工表也没有更新,最终值保持不变。

Hibernate的update方法

应该在我们只更新实体信息的情况下使用Hibernate update。执行此操作会将实体对象添加到持久化上下文中,并在事务提交时跟踪和保存进一步的更改。让我们通过一个简单的程序来检查这种行为。

package com.Olivia.hibernate.main;

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

import com.Olivia.hibernate.model.Employee;
import com.Olivia.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();

	}

}

当我们第一次执行上述程序时,我们会得到以下输出。

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

注意到首次执行后没有触发更新,因为值没有发生变化。另外,值得注意的是,员工名字是我们在调用update()方法后设置的“最终更新的名字”。这证明了Hibernate一直在追踪对象的任何变化,并在提交事务时保存了这个值。

合并持久化

通过回合并并(merge)可以用来更新现有的值,不过这个方法会从传递的实体对象创建一个副本并返回它。返回的对象是持久上下文的一部分,并跟踪任何更改,而传递的对象不被跟踪。这是merge()方法与其他所有方法的主要区别。让我们通过一个简单的程序来了解这一点。

package com.Olivia.hibernate.main;

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

import com.Olivia.hibernate.model.Employee;
import com.Olivia.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("员工对象已加载。 " + emp);
		tx.commit();

		 //合并示例 - 表中已存在数据
		 emp.setSalary(25000);
		 Transaction tx8 = session.beginTransaction();
		 Employee emp4 = (Employee) session.merge(emp);
		 System.out.println(emp4 == emp); // 返回false
		 emp.setName("测试");
		 emp4.setName("Kumar");
		 System.out.println("15. 提交合并事务之前");
		 tx8.commit();
		 System.out.println("16. 提交合并事务之后");

		// 关闭资源
		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=?
员工对象已加载。 Id= 101, 姓名= 最终更新姓名, 薪资= 1000.0, {地址= 地址行1= 测试地址1, 城市=班加罗尔, 邮编=12121}
false
15. 提交合并事务之前
Hibernate: update EMPLOYEE set emp_name=?, emp_salary=? where emp_id=?
16. 提交合并事务之后

再次执行程序产生的输出结果如下:

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=?
员工对象已加载。 Id= 101, 姓名= Kumar, 薪资= 25000.0, {地址= 地址行1= 测试地址1, 城市=班加罗尔, 邮编=12121}
false
15. 提交合并事务之前
16. 提交合并事务之后

请注意,merge()返回的实体对象与传入的实体对象不同。另外,在再次执行中,姓名变为”Kumar”,这是因为返回的对象被持久化上下文跟踪任何更改。以上就是关于Hibernate Session的保存和更新方法的全部内容,希望这些示例能够帮助您解决相关疑问。

bannerAds