How to map an immutable entity with JPA and Hibernate
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
In this article, you are going to find out how to map an immutable entity when using JPA and Hibernate.
If your Domain Model requires that a given entity should not be modified by the data access logic, Hibernate can enforce this requirement if the entity is marked with the @Immutable annotation.
Domain Model
Assuming you have the following Event entity:
Because the Event entity is meant to be immutable, there is no setter method, and a single public constructor takes all the entity properties that need to be initialized:
@Entity(name = "Event")
@Immutable
public class Event {
@Id
private Long id;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn = new Date();
@Column(name = "event_key")
private String eventKey;
@Column(name = "event_value")
private String eventValue;
public Event(
Long id,
String eventKey,
String eventValue) {
this.id = id;
this.eventKey = eventKey;
this.eventValue = eventValue;
}
//Needed when instantiating the entity from a JDBC ResultSet
private Event() {
}
}
Notice that the Event entity is marked with the @Immutable annotation since we don’t Hibernate to track Event entity modifications.
Preventing entity modifications
Even if the Event entity features no setter method, we can still change an Event entity instance via Java Reflection:
doInJPA(entityManager -> {
Event event = entityManager.find(Event.class, 1L);
assertEquals(
"25",
event.getEventValue()
);
ReflectionUtils.setFieldValue(event, "eventValue", "10");
assertEquals("10", event.getEventValue());
});
doInJPA(entityManager -> {
Event event = entityManager.find(Event.class, 1L);
assertEquals(
"25",
event.getEventValue()
);
});
However, when running the test case above, you will see that Hibernate issues no UPDATE statement since the Event entity is marked with the @Immutable annotation.
The reason why Hibernate does not track
@Immutableentity modifications is because the entity is loaded in read-only mode, hence thedetachedStateorhydratedStateis never stored in the currently running Persistence Context.
JPQL update queries
Prior to Hibernate 5.2.17, JPQL queries were not taking into consideration the @Immutable status of a given entity.
In Hibernate 5.2.17, a WARNING message is logged when we try to modify the Event entity via a JPQL bulk update statement. Therefore, when running the following JPQL update query:
entityManager.createQuery("""
update Event
set eventValue = :eventValue
where id = :id
""")
.setParameter("eventValue", "10")
.setParameter("id", 1L)
.executeUpdate();
Hibernate generates the following output:
WARN HHH000487: The query: [ update Event set eventValue = :eventValue where id = :id ] attempts to update an immutable entity: [Event] Query:["update Event set event_value=? where id=?"], Params:[(10, 1)]
Although the UPDATE statement is generated, there is now a WARNING message printed in the log.
If logging a WARN log entry is not sufficient for you and you want to prevent such modifications,
you can provide the following Hibernate configuration property:
hibernate.query.immutable_entity_update_query_handling_mode=exception
Now, when running the previous JPQL query, a Hibernate exception is thrown:
try {
doInJPA(entityManager -> {
entityManager.createQuery("""
update Event
set eventValue = :eventValue
where id = :id
""")
.setParameter("eventValue", "10")
.setParameter("id", 1L)
.executeUpdate();
});
fail("Should have thrown exception");
} catch (Exception e) {
HibernateException cause = (HibernateException) e.getCause();
assertTrue(
cause.getMessage().contains(
"attempts to update an immutable entity: [Event]"
)
);
}
Criteria API bulk update queries
The same goes for the Criteria API. By default, from Hibernate 5.2.17 onwards, a WARNING will be issued when executing the following Criteria API query:
CriteriaBuilder builder = entityManager
.getCriteriaBuilder();
CriteriaUpdate<Event> update = builder
.createCriteriaUpdate(Event.class);
Root<Event> root = update.from(Event.class);
update
.set(root.get("eventValue"), "100")
.where(
builder.equal(root.get("id"), 1L)
);
entityManager
.createQuery(update)
.executeUpdate();
Hibernate logs the following output:
-- HHH000487: The query: [ update Event as generatedAlias0 set generatedAlias0.eventValue = :param0 where generatedAlias0.id=1L ] attempts to update an immutable entity: [Event] Query:["update Event set event_value=? where id=1"], Params:[(100)]
However, when switching to the exception mode:
hibernate.query.immutable_entity_update_query_handling_mode=exception
A HibernateException will be thrown to prevent the SQL UPDATE from being executed:
try {
doInJPA(entityManager -> {
CriteriaBuilder builder = entityManager
.getCriteriaBuilder();
CriteriaUpdate<Event> update = builder
.createCriteriaUpdate(Event.class);
Root<Event> root = update.from(Event.class);
update
.set(root.get("eventValue"), "100")
.where(
builder.equal(root.get("id"), 1L)
);
entityManager.createQuery(update).executeUpdate();
});
fail("Should have thrown exception");
} catch (Exception e) {
HibernateException cause = (HibernateException) e.getCause();
assertTrue(
cause.getMessage().contains(
"attempts to update an immutable entity: [Event]"
)
);
}
Cool, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Mapping an immutable entity with Hibernate is a trivial thing to do, and Hibernate 5.2.17 brings a new entity update query handling mode that prevents entity modifications via JPQL or Criteria API.







