How to map calculated properties with Hibernate @Generated annotation

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 time
  • ALWAYS – 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 as well.

Conclusion

Mapping calculated entity properties is fairly easy with Hibernate, and you have multiple ways to achieve this goal.

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

4 thoughts on “How to map calculated properties with Hibernate @Generated annotation

  1. Hello Vlad,
    the exploitation is very helpful for me.
    But I also have a more sophisticated use case: In a parent-child one-to-many relation I would like to filter parent that have a exact number of children. Can I do that also with the Formula annotation?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s