After reading this article you should be aware of how to make an entity immutable with the Hibernate framework. You’ll also learn why it is important and what you need to watch out for when working with immutable entities. The source code for this example is available on allaroundjava GitHub.
This article assumes you know the basics of Hibernate setup. If you need more guidance in this matter, please see the following article.

Immutability

Immutable classes are classes which state remains unchanged throughout the application lifetime. The set of class instance properties is assigned at creation and property values are never allowed to change. In other words, immutable class instances, once created, can never be modified. Because of this property, they are safe to be passed around various application services as well as threads. They are in fact read only.
Immutable objects are great in Sets and make great Map keys. They are simple, secure and thread safe. As Joshua Bloch says in Effective Java, “Classes should be immutable unless there’s a very good reason to make them mutable”.
You definitely came across immutable classes in standard Java packages. String, Long or LocalDate are examples of such classes.

Making classes immutable

Joshua Bloch lists the following five principles to keep in mind when designing immutable classes:

  1. Avoid providing any methods which modify object state. Obvious candidates are property setters as well as any other methods adjusting existing properties.
  2. Make all fields private – to avoid modifying them directly, especially if they are reference variables.
  3. Make all fields final – to explicitly express intent that their values should not change. This also means all the properties need to be assigned at the moment of creation in constructor.
  4. Ensure class cannot be extended – this eliminates a possibility to expose its variables indirectly through a child class. It can be accomplished by making the class final or by providing a private constructor. Static factory method or a builder class is used to instantiate objects in such a scenario.
  5. Ensure no outside class can modify mutable reference variables. Imagine your class has a list property. Even though final, the contents of the list can be easily modified. Final refers to the fact that the variable cannot be assigned any other list instance, but it ignores the fact that list contents need to stay untouched. In order to ensure immutability, no external class can initialize such a variable or retrieve a reference to it. It is usually accomplished with defensive copying such property.

Here’s an example of an immutable POJO class which applies all five rules. We can return references to id, amount, and timestamp as they’re instances of immutable classes.


public final class FinancialTransaction {

    private final Long id;
    private final BigDecimal amount;
    private final Instant timestamp;

    public FinancialTransaction(Long id, BigDecimal amount, Instant timestamp) {
        this.id = id;
        this.amount = amount;
        this.timestamp = timestamp;
    }

    public Long getId() {
        return id;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public Instant getTimestamp() {
        return timestamp;
    }  
}

Immutable entities with Hibernate

There’s one obstacle on the way of creating immutable entity classes with an ORM framework like Hibernate. Hibernate uses reflection to instantiate entities, it, therefore requires a default, no argument constructor. This means class fields can never be final.
Providing both a default, no argument constructor which is not really usable, along with a parametrized constructor is not quite elegant. I”ll go with a static factory method to construct an entity instance.
Moreover, when an entity class is lazily loaded, Hibernate replaces its instance with a proxy class. For that, it needs an entity constructor to be at least package private.
Here’s how the above class needs to be altered to become an immutable entity.


@Entity
@Table(name = "FINANCIAL_TRANSACTION")
public final class FinancialTransaction {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private BigDecimal amount;
    @Column(nullable = false)
    private Instant timestamp;

    FinancialTransaction() {
    }

    FinancialTransaction(BigDecimal amount, Instant timestamp) {
        this.amount = amount;
        this.timestamp = timestamp;
    }

    public static FinancialTransaction newInstance(BigDecimal amount, Instant timestamp) {
        return new FinancialTransaction(amount, timestamp);
    }

    //getters

Our entity class does not have any mutators, all of its fields are private and it cannot be extended. We’re not accepting or returning any reference to mutable property, which means we’re quite safe.
Lack of final fields does no harm unless we want to modify them from inside the class. This hardly ever happens for entities.
Please also note that the id property is autogenerated, hence no longer assigned in the constructor.

Altering immutable entity

Reasonably speaking, FinancialTransaction is sufficiently immutable. Let’s see how Hibernate sees the entity when its properties are altered. This test assumes that a record with id equal 1 exists in the database.


@Test
public void havingImmutableEntity_whenMerge_thenNoUpdate() 
        throws NoSuchFieldException, IllegalAccessException {
    Optional transactionOptional = finTransactionDao.getById(1L);
    FinancialTransaction transaction = transactionOptional.get();
    Field amountField = transaction.getClass().getDeclaredField("amount");
    amountField.setAccessible(true);
    BigDecimal newAmount = BigDecimal.valueOf(1900);
    amountField.set(transaction, newAmount);
    finTransactionDao.merge(transaction);

    FinancialTransaction retrievedTransaction = finTransactionDao.getById(1L).get();
    Assert.assertNotEquals(newAmount, retrievedTransaction.getAmount());
}

Above test uses reflection to modify FinancialTransaction’s private amount property. Unfortunately Hibernate will fail to recognize that we intended FinancialTransaction to be immutable and the test will fail.
For such cases, Hibernate introduced its proprietary @Immutable annotation. Proprietary – meaning it’s not part of JPA standard. When @Immutable is present on an entity class, no update statements are executed against an entity when Persistence Context is flushed. In other words, a database record created with @Immutable entity, will never be updated.


@Entity
@Table(name = "FINANCIAL_TRANSACTION")
@org.hibernate.annotations.Immutable
public final class FinancialTransaction {
    //fields
    //getters
    //static factory
}

Immutable entity with a one to many relation

Consider the following entity class


@Entity
@Table(name = "RECEIPT")
@org.hibernate.annotations.Immutable
public final class Receipt {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, name = "TRANSACTION_DATE")
    private LocalDateTime transactionDate;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "RECEIPT_ID")
    private Set lineItems;

    Receipt() {

    }

    Receipt(LocalDateTime transactionDate, Set lineItems) {
        this.transactionDate = transactionDate;
        this.lineItems = lineItems;
    }

    public Long getId() {
        return id;
    }

    public LocalDateTime getTransactionDate() {
        return transactionDate;
    }

    public Set getLineItems() {
        return lineItems;
    }

    public static Receipt newInstance(LocalDateTime transactionDate, Set transactions) {
        return new Receipt(transactionDate, Collections.unmodifiableSet(transactions));
    }
}

Now consider test method modifying the contents of above @Immutable marked entity.


@Test
public void havingImmutableRecept_whenModifyingLineItems_thenNoUpdate() {
    Optional receiptOptional = receiptDao.getById(1L);
    Receipt receipt = receiptOptional.get();
    int lineItemcount = receipt.getLineItems().size();

    LineItem item = LineItem.getBuilder()
            .withItemName("mayo")
            .withPrice(BigDecimal.TEN)
            .withQuantity(2)
            .build();

    receipt.getLineItems().add(item);
    receiptDao.merge(receipt);

    Receipt retrievedReceipt = receiptDao.getById(1L).get();
    Assert.assertEquals(lineItemcount, retrievedReceipt.getLineItems().size());
}

The Receipt entity breaks immutability on purpose by returning a reference to its set of line items. This is against point 5 Joshua Bloch’s list. But the entity is marked @Immutable, there should be no update statement sent to it regardless right?
Well, that’s a good question. If you look at database tables, no records in RECEIPT table got modified.

Immutable entity one to many relation

Because LineItems are marked with CascadeType.ALL, any change in their contents (within Receipt) will be persisted when flushing the Persistence Context.

There are two ways to handle this:

  1. Apply Hibernate’s @Immutable annotation on the collection.
    
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "receipt_id")
    @org.hibernate.annotations.Immutable
    private Set lineItems;
    

    With this setup, calling the merge() method in above test will result in an exception.

    
    org.hibernate.HibernateException: 
    changed an immutable collection instance: 
    [com.allaroundjava.model.Receipt.lineItems#1]
    

    Instead of silently skipping the persistence of additional items in a set, Hibernate is throwing an exception. If that wasn’t the case, Receipt contents in application memory and in database would not match.

  2. Return a defensive, immutable copy.
    
    public Set getLineItems() {
        return Collections.unmodifiableSet(lineItems);
    }
    

    This Set implementation will disallow any modifications made to its contents. Attempt to do so, will result in UnsupportedOperationException at the moment of calling add() on Set. This approach is also very verbose, allowing to discover issues early. It also brings a good habit of returning a defensive copy of a referenced collection property.

Immutable entity with a reference to mutable entity

With POJO classes, according to point 5 in immutability rules list, we protect against returning a reference to any class’ properties or accepting a reference variable from the outside world.
Let’s consider two classes – immutable Passport entity and a mutable Person entity and the following test method.


@Test
public void havingImmutablePassport_whenPersonModified_thenPassportPersonModified() {
    Person person = new Person();
    person.setName("Mary");
    personDao.persist(person);

    Passport passport = Passport.newInstance("ABC1234", person);
    passportDao.persist(passport);

    person.setName("John");
    personDao.merge(person);

    Assert.assertEquals("John", passport.getPerson().getName());
}

From the point of view of POJO immutability Passport accepting Person reference without a defensive copy is a big no-no. With entities, we can probably argue. The fact that Passport entity is composed of (among others) Person really translates to a relation between two database tables. A row in one table carries a foreign key to a row in another table. This relationship remains untouched in this test. Passport entity remained in its original state, with same passport number and a person it was referring to.

Immutable entity with 1-1 relation

Please bear in mind that @Immutable annotation ensures immutability on a single entity level.
Thinking about bigger applications, entities typically carry several relations, some of them bi-directional. Moreover, entities which are related often have their own Dao classes, allowing to modify them independently. In order to say that entity class is truly immutable, all of its entity relations need to be immutable as well. @Immutable annotation is not quite enough.
Bearing that in mind, some care around using immutable entities across application services or threads needs to be taken. Please also see the next example.

The identity column issue

In first paragraphs we said that immutable classes make great Map keys. This is definitely true, however there’s one word of caution required. Lets come back to FinancialTransaction immutable entity with its hashCode() and equals() overriden.


@Entity
@Table(name = "FINANCIAL_TRANSACTION")
@org.hibernate.annotations.Immutable
public final class FinancialTransaction {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private BigDecimal amount;
    @Column(nullable = false)
    private Instant timestamp;

    FinancialTransaction() {
    }

    FinancialTransaction(BigDecimal amount, Instant timestamp) {
        this.amount = amount;
        this.timestamp = timestamp;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FinancialTransaction)) return false;

        FinancialTransaction that = (FinancialTransaction) o;

        if (id != null ? !id.equals(that.id) : that.id != null) return false;
        if (!amount.equals(that.amount)) return false;
        return timestamp.equals(that.timestamp);
    }

    @Override
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + amount.hashCode();
        result = 31 * result + timestamp.hashCode();
        return result;
    }

    //getters
    //static factory
}

This is how this entity will behave when added to a Set and persisted.


@Test
public void havingImmutableEntity_whenPersist_thenHashCodeChanges() {
    Set financialTransactions = new HashSet<>();
    Instant timestamp = Instant.now();
    FinancialTransaction one =
            FinancialTransaction.newInstance(BigDecimal.TEN, timestamp);
    financialTransactions.add(one);
    financialTransactions.add(one);
    Assert.assertEquals(1, financialTransactions.size());

    finTransactionDao.persist(one);
    financialTransactions.add(one);
    Assert.assertEquals(2, financialTransactions.size());
}

We mentioned above that immutable entity can reside in only one state. This condition does not hold for an entity with autogenerated id column value. Please note that id field is not assigned in constructor. Its value is assigned by Hibernate, using reflection, when an entity is persisted. Because id is included in hash code calculation, this fact alters its hashCode() and equals() result.
The id property is usually the easiest to compare when checking for entity equality, you need to be aware that this property state changes when it is auto-generated.

Take a look here for some more knowledge

Leave a Comment