@Immutable and @NaturalId – Hibernate-Specific Annotations

In previous post, we learned about most commonly used JPA annotations in hibernate and we also learned there about the hibernate annotations which complement those JPA annotations. Using JPA annotations makes your application code portable to other JPA implementations which is a good thing to have. Apart from JPA annotations, hibernate have some of it’s own annotations which you can use to have certain features in application code. But you must remember that it may make hard to make your code portable in future date.

Read More : JPA 2 Persistence Annotations Tutorial

In this post, we are going to learn about two such annotations which are hibernate specific.

1) @Immutable Annotation

The @Immutable annotation marks an entity as being, well, immutable. This is useful for situations in which your entity represents reference data–things like lists of states, genders, or other rarely mutated data.

Since things like states tend to be rarely changed, someone usually updates the data manually, via SQL or an administration application. Hibernate can cache this data aggressively, which needs to be taken into consideration; if the reference data changes, you’d want to make sure that the applications using it are notified (may use refresh() method) or restarted somehow.

What @Immutable annotation tells Hibernate is that any updates to an immutable entity should not be passed on to the database without giving any error. @Immutable can be placed on a collection too; in this case, changes to the collection (additions, or removals) will cause a HibernateException to be thrown.

EmployeeEntity.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

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 Annotation

We learned so much things in past tutorials about @Id annotation with @GeneratedValue to create primary keys for records in database. In most real life applications, these primary keys are “artificial primary keys” and referred only inside application runtime. However, there’s also the concept of a “natural ID“, which provides another convenient and logical way to refer to an entity, apart from an artificial or composite primary key.

An example of natural id might be a Social Security number or a Tax Identification Number in the United States, and PAN number in India. An entity (being a person or a corporation) might have an artificial primary key generated by Hibernate, but it also might have a unique tax identifier. Hibernate allows you to search and load entities based on these natural ids as well.

For natural IDs, there are two forms of load mechanisms; one uses the simple natural ID (where the natural ID is one and only one field), and the other uses named attributes as part of a composite natural ID.

Simple Natural Id Example

@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


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=?

Please watch closely that in case of entity already not present in session, and if you get entity using it’s natural id then first primary id is fetched using natural id; and then entity is fetched using this primary id. If entity is already present in session, then reference of same entity is returned without executing additional SELECT statements in database.

Composite Natural Id Example

@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


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

Entity fetching logic for composite natural id is same as simple natural id. No difference apart from use of multiple natural keys instead one.

That’s all for these pretty good to know annotations. Keep posting about your thoughts in comments.

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

Leave a Comment

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.