How to map calculated properties with Hibernate @Generated annotation
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
As I explained in this previous article, you can map calculated properties using Hibernate @Formula, and the value is generated at query time.
In this post, you’ll see how you can calculate an entity property at INSERT or UPDATE time.
Domain Model
Assuming we have the following Hero entity mapping:
@Entity(name = "Hero")
public class Hero {
@Id
private Long id;
private String firstName;
private String lastName;
private String middleName1;
private String middleName2;
private String middleName3;
private String middleName4;
private String middleName5;
@Generated( value = GenerationTime.ALWAYS )
@Column(columnDefinition =
"AS CONCAT(" +
" COALESCE(firstName, ''), " +
" COALESCE(' ' + middleName1, ''), " +
" COALESCE(' ' + middleName2, ''), " +
" COALESCE(' ' + middleName3, ''), " +
" COALESCE(' ' + middleName4, ''), " +
" COALESCE(' ' + middleName5, ''), " +
" COALESCE(' ' + lastName, '') " +
")")
private String fullName;
//Getters and setters omitted for brevity
public String getFullName() {
return fullName;
}
}
The fullName property is calculated using the SQL Server CONCAT function, by including all names. The COALESCE function is used because CONCAT does not take a separator in SQL Server.
The columnDefinition is only useful when generating the database schema from JPA annotations (which you shouldn’t do in a production environment). Because we provided a custom @Column definition, the underlying database table looks like this:
CREATE TABLE Hero
(
id BIGINT NOT NULL ,
firstName VARCHAR(255) ,
fullName AS CONCAT(COALESCE(firstName, ''),
COALESCE(' ' + middleName1, ''),
COALESCE(' ' + middleName2, ''),
COALESCE(' ' + middleName3, ''),
COALESCE(' ' + middleName4, ''),
COALESCE(' ' + middleName5, ''),
COALESCE(' ' + lastName, '')) ,
lastName VARCHAR(255) ,
middleName1 VARCHAR(255) ,
middleName2 VARCHAR(255) ,
middleName3 VARCHAR(255) ,
middleName4 VARCHAR(255) ,
middleName5 VARCHAR(255) ,
PRIMARY KEY ( id )
)
The @Generated annotation is used to instruct Hibernate when the associated column value is calculated, and it can take two values:
INSERT– meaning that the column value is calculated at insert timeALWAYS– meaning that the column value is calculated both at insert and update time
Testing time
Now, when saving a Hero entity:
doInJPA( entityManager -> {
Hero heroine = new Hero();
heroine.setId( 1L );
heroine.setFirstName( "Agustina" );
heroine.setMiddleName1( "Raimunda" );
heroine.setMiddleName2( "María" );
heroine.setMiddleName3( "Saragossa" );
heroine.setLastName( "Domènech" );
entityManager.persist( heroine );
LOGGER.info("After entity persist action");
entityManager.flush();
assertEquals(
"Agustina Raimunda María Saragossa Domènech",
heroine.getFullName()
);
} );
Hibernate generates the following SQL statements:
-- After entity persist action
INSERT INTO Hero
(firstName, lastName, middleName1, middleName2, middleName3, middleName4, middleName5, id)
VALUES
('Agustina', 'Domènech', 'Raimunda', 'María', 'Saragossa', NULL(VARCHAR), NULL(VARCHAR), 1)
SELECT
h.fullName as fullName3_0_
FROM Hero h
WHERE h.id = 1
Note the SELECT query that is issued after the flush operation, which allows Hibernate to fetch the calculated entity property.
When updating and loading the entity:
doInJPA( entityManager -> {
Hero heroine = entityManager.find( Hero.class, 1L );
heroine.setMiddleName1( null );
heroine.setMiddleName2( null );
heroine.setMiddleName3( null );
heroine.setLastName( "de Aragón" );
LOGGER.info("After entity update action");
entityManager.flush();
assertEquals("Agustina de Aragón", heroine.getFullName());
} );
Hibernate generates the following SQL statements:
SELECT
h.id AS id1_0_0_,
h.firstName AS firstNam2_0_0_,
h.fullName AS fullName3_0_0_,
h.lastName AS lastName4_0_0_,
h.middleName1 AS middleNa5_0_0_,
h.middleName2 AS middleNa6_0_0_,
h.middleName3 AS middleNa7_0_0_,
h.middleName4 AS middleNa8_0_0_,
h.middleName5 AS middleNa9_0_0_
FROM Hero h
WHERE
h.id = 1
UPDATE Hero
SET
firstName = 'Agustina',
lastName = 'de Aragón',
middleName1 = NULL(VARCHAR),
middleName2 = NULL(VARCHAR),
middleName3 = NULL(VARCHAR),
middleName4 = NULL(VARCHAR),
middleName5 = NULL(VARCHAR)
WHERE
id = 1
-- After entity update action
SELECT
h.fullName as fullName3_0_
FROM Hero h
WHERE h.id = 1
Again, the last SELECT query is used to ensure that the managed entity is in sync with the underlying table row.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Mapping calculated entity properties is fairly easy with Hibernate, and you have multiple ways to achieve this goal.
The @Generated annotation allows you to build entity properties dynamically upon insert or update based on other property values.






