# `@Immutable`和`@NaturalId` – 特定于 Hiberate 的注解

> 原文： [https://howtodoinjava.com/hibernate/immutable-and-naturalid-hibernate-specific-annotations/](https://howtodoinjava.com/hibernate/immutable-and-naturalid-hibernate-specific-annotations/)

在上一篇文章中，我们了解了[Hiberate](//howtodoinjava.com/hibernate-tutorials/ "Hibernate Tutorials")中最常用的 [JPA 注解](//howtodoinjava.com/hibernate/hibernate-jpa-2-persistence-annotations-tutorial/ "Hibernate / JPA2 Persistence Annotations Tutorial")，还了解了与这些 JPA 注解互补的 Hiberate 注解。 使用 JPA 注解使您的应用代码可移植到其他 JPA 实现中，这是一件好事。 除了 JPA 注解之外，Hiberate 还具有一些自己的注解，您可以使用它们在应用代码中具有某些功能。 但是您必须记住，将来的日期可能很难使您的代码可移植。

> **阅读更多： [JPA2 持久化注解教程](//howtodoinjava.com/hibernate/hibernate-jpa-2-persistence-annotations-tutorial/ "Hibernate / JPA2 Persistence Annotations Tutorial")**

在本文中，我们将学习两个特定于 Hibernate 的此类注解。

## 1）`@Immutable`注解

`@Immutable`注解将一个实体标记为不可变。 这在您的实体表示参考数据的情况下非常有用，例如状态列表，性别或其他很少突变的数据。

由于状态之类的东西很少会更改，因此通常有人会通过 SQL 或管理应用手动更新数据。 Hibernate 可以积极地缓存此数据，需要将其考虑在内。 如果参考数据发生变化，则需要确保已通知使用该数据的应用（可以使用`refresh()`方法）或以某种方式重新启动。

`@Immutable`注解告诉 Hibernate，对不可变实体的任何更新都不应传递给数据库而不会给出任何错误。 `@Immutable`也可以放在集合上； 在这种情况下，对集合的更改（添加或删除）将引发`HibernateException`。

**`EmployeeEntity.java`**

```java
import org.hibernate.annotations.Immutable;

@Immutable
@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@Column(name = "ID", unique = true, nullable = false)
	private Integer           employeeId;
	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String            firstName;
	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String            lastName;

	//Setters and Getters
}

```

**`ImmutableAnnotationExample.java`**

```java
public class ImmutableAnnotationExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Load the employee in another session
      EmployeeEntity employee = (EmployeeEntity) sessionOne.load(EmployeeEntity.class, 1);

      //Update the first name
      employee.setFirstName("Alex");
      sessionOne.flush();
      sessionOne.close();

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //Load the employee in another session
      EmployeeEntity employeeUpdated = (EmployeeEntity) sessionTwo.load(EmployeeEntity.class, 1);

      //Verify the first name
      System.out.println(employeeUpdated.getFirstName());

      sessionTwo.flush();
      sessionTwo.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
from Employee employeeen0_ where employeeen0_.ID=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
from Employee employeeen0_ where employeeen0_.ID=?

Lokesh //Value didn't updated in database i.e. immutable

```

## 2）`@NaturalId`注解

在过去的教程中，我们了解了很多有关`@Id`和`@GeneratedValue`注解以为数据库中的记录创建主键的知识。 在大多数实际应用中，这些主键是“***人工主键***”，并且仅在应用运行时内部引用。 但是，还存在“**自然 ID**”的概念，除了人工或复合主键之外，它还提供了另一种方便且合乎逻辑的方式来引用实体。

**自然 ID 的示例可能是美国的社会安全号码或税号，而印度则是 PAN 号码。** 实体（是个人或公司）可能具有由 Hibernate 生成的人为主键，但也可能具有唯一的税标识符。 Hibernate 还允许您基于这些自然 ID 搜索和加载实体。

对于自然 ID，有两种形式的加载机制： 一种使用简单自然 ID（其中自然 ID 是一个且仅一个字段），另一种使用命名属性作为复合自然 ID 的一部分。

#### 简单的自然 ID 示例

```java
@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   //Natural id can be SSN as well
   @NaturalId
   Integer SSN;

   //Setters and Getters
}
```

`SimpleNaturalIdExample.java`

```java
public class SimpleNaturalIdExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Load the employee
      EmployeeEntity employee1 = (EmployeeEntity) sessionOne.load(EmployeeEntity.class, 1);
      //Just to ensure that employee is loasded from DB
      System.out.println(employee1.getFirstName());

      //Get the employee for natural id i.e. SSN; This does not execute another SQL SELECT as entity is already present in session
      EmployeeEntity employee2 = (EmployeeEntity) sessionOne.bySimpleNaturalId(EmployeeEntity.class).load(12345);

      //Verify that employee1 and employee2 refer to same object
      assert(employee1 == employee2);

      sessionOne.flush();
      sessionOne.close();

      System.out.println("====================================");

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //Get the employee for natural id i.e. SSN; entity is not present in this session
      EmployeeEntity employee = (EmployeeEntity) sessionTwo.bySimpleNaturalId(EmployeeEntity.class).load(12345);

      sessionTwo.flush();
      sessionTwo.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      emp.setSSN(12345);
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (SSN, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.SSN as SSN2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_,
 employeeen0_.LAST_NAME as LAST_NAM4_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

Lokesh
====================================
Hibernate: select employeeen_.ID as ID1_1_ from Employee employeeen_ where employeeen_.SSN=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.SSN as SSN2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_, 
employeeen0_.LAST_NAME as LAST_NAM4_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

```

请密切注意如果会话中不存在实体，并且如果您使用实体的自然 ID 获取实体，则使用自然 ID 提取第一个主要 ID； 然后使用此主要 ID 提取实体。 **如果会话中已经存在实体，则返回相同实体的引用**，而无需在数据库中执行其他`SELECT`语句。

#### 复合自然 ID 示例

```java
@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   //Natural id part 1
   @NaturalId
   Integer seatNumber;

   //Natural id part 2
   @NaturalId
   String departmentName;

   //Setters and Getters
}
```

`CompositeNaturalIdExample.java`

```java
public class CompositeNaturalIdExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Get the employee for natural id i.e. SSN; entity is not present in this session
      EmployeeEntity employee = (EmployeeEntity) sessionOne.byNaturalId(EmployeeEntity.class)
                                                            .using("seatNumber", 12345)
                                                            .using("departmentName", "IT")
                                                            .load();

      System.out.println(employee.getFirstName());

      sessionOne.flush();
      sessionOne.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      emp.setSeatNumber(12345);
      emp.setDepartmentName("IT");
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (departmentName, FIRST_NAME, LAST_NAME, seatNumber, ID) values (?, ?, ?, ?, ?)

Hibernate: select employeeen_.ID as ID1_1_ from Employee employeeen_ where employeeen_.departmentName=? and employeeen_.seatNumber=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.departmentName as departme2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_, 
employeeen0_.LAST_NAME as LAST_NAM4_1_0_, employeeen0_.seatNumber as seatNumb5_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

Lokesh

```

复合自然 ID 的实体获取逻辑与简单自然 ID 相同。 除了使用多个自然键而不是一个以外，没有区别。

这些就是所有这些众所周知的注解。 继续在评论中发表您的想法。

**祝您学习愉快！**