How to update only a subset of entity attributes using JPA and Hibernate

(Last Updated On: January 4, 2018)

Introduction

One of my Twitter followers asked me to answer the following question on StackOverflow.

This article aims to explain various techniques for controlling the UPDATE SQL statement that is executed whenever an entity is modified by the data access layer.

Domain Model

Let’s assume we have the following Post entity:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;

    private long score;

    @Column(
        name = "created_on", 
        nullable = false, 
        updatable = false
    )
    private Timestamp createdOn;

    @Transient
    private String creationTimestamp;

    public Post() {
        this.createdOn = new Timestamp(
            System.currentTimeMillis()
        );
    }

    public String getCreationTimestamp() {
        if(creationTimestamp == null) {
            creationTimestamp = DateTimeFormatter
            .ISO_DATE_TIME.format(
                createdOn.toLocalDateTime()
            );
        }
        return creationTimestamp;
    }

    @Override
    public String toString() {
        return String.format(
            "Post{\n" +
            "  id=%d\n" +
            "  title='%s'\n" +
            "  score=%d\n" +
            "  creationTimestamp='%s'\n" +
            '}', id, title, score, getCreationTimestamp()
        );
    }

    //Getters and setters omitted for brevity
}

Insert-only columns

The Post entity has a createdOn attribute which should only be set when the entity is created for the first time. Any successive UPDATE is not allowed to modify this database column, therefore the updatable attribute of the associated @Column annotation is set to false.

@Column(
    name = "created_on", 
    nullable = false, 
    updatable = false
)
private Timestamp createdOn;

For entity attribute that should never modify after the entity is persisted (insert-only columns), you should use @Column(updatable = false).

Calculated entity attributes

The Post entity createdOn attribute is a Timestamp which we might want to print in application logs using the ISO_DATE_TIME DateTimeFormatter. To avoid calculating the String object representation every time we need to log this entity, the creationTimestamp attribute is going to store this value.

@Transient
private String creationTimestamp;

However, we don’t want this attribute to be persisted. Hence, we need to annotate it with the @Transient. This way, Hibernate is going to ignore this attribute when translating the entity state modifications into an SQL statement.

Assuming we have persisted the following entity:

doInJPA(entityManager -> {
    Post post = new Post();
    post.setId(1L);
    post.setTitle("High-Performance Java Persistence");
    entityManager.persist(post);
});

When fetching, logging, and modifying this entity:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);
    LOGGER.info("Fetched post: {}", post);
    post.setScore(12);
});

The following output is obtained:

SELECT p.id AS id1_0_0_,
       p.created_on AS created_2_0_0_,
       p.score AS score3_0_0_,
       p.title AS title4_0_0_
FROM   post p
WHERE  p.id = 1

-- Fetched post: Post{
  id=1
  title='High-Performance Java Persistence'
  score=0
  creationTimestamp='2016-10-10T16:48:25.566'
}

UPDATE post
SET    score = 12,
       title = 'High-Performance Java Persistence'
WHERE  id = 1

As you can see, neither the createdOn not the creationTimestamp are included in the UPDATE SQL statement.

For calculated attributes that should never be persisted in the associated database table, you should use @Transient.

Dynamic updates

The previous UPDATE statement includes all table columns, even if only a subset is being actually modified. Using the same SQL statement is beneficial when using JDBC statement caching. However, if the database table is heavily indexed, we don’t want to update certain index entries that have not been modified, as explained by Markus Winand.

For this reason, Hibernate offers the @DynamicUpdate annotation. All we need to do is to add this annotation at the entity level:

@Entity(name = "Post")
@Table(name = "post")
@DynamicUpdate
public class Post {
    //Code omitted for brevity
}

Now, when executing the previous test case which modified the score attribute, the following UPDATE statement is executed:

UPDATE post
SET    score = 12,
WHERE  id = 1

Brilliant!

To update only the columns that got modified, the entity must use the @DynamicUpdate annotation, which is also mandatory when using versionless optimistic locking as well.

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Flexibility is very important when dealing with a data access framework, and, for this reason, Hibernate offers several options to avoid updating certain columns, or to update just the columns that got modified.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

21 thoughts on “How to update only a subset of entity attributes using JPA and Hibernate

  1. What if I merge a detached post to update it, set the createdOn column as updatable = false, but would like to return the actual createdOn value? I tried out @DynamicUpdate with @SelectBeforeUpdate but with no effect, the post returned form merge still has the createdOn property set to null, even though I can see in the logs that the createdOn column is included in the generated select statement.

    1. the post returned form merge still has the createdOn property set to null, even though I can see in the logs that the createdOn column is included in the generated select statement.

      I don’t understand what you are asking. @DynamicUpdate is for including only the modifying columns in UPDATE clause.

      If you have an updateable=false property, it does not matter what version of UPDATE you want to use because this field is not going to be included.

      So, both the detached and the merged entity should have the createdOn property.

      Try replicating it with the JPA test case templates.

      1. What I mean is, if the merged (returned) Post entity can have all fields populated from the database, even if the detached one only has the id and the updated title.

        I receive a post with id and title from a web request, I merge it, and than I would like to return the merged post with id, title and createdOn.

        I know that I can just find that post by id and set the new title, which will result in issuing an update statement, and then return the found entity to the response (with the createdOn date), but I was just wandering if I can do it with a merge.

      2. If the detached has only two properties set and the rest are set to null, and you do an EntityManager#merge, Hibernate will set to null or the properties which are null now.

        If you don’t want to do that, you have to:

        1. Either do a manual merge so that you transpose the incoming DTO to the latest entity state that’s fetched from the database,

        2. You use the subetntity for the merge part, but you return the full entity for the response.

  2. Ok, so will that merge fail in the case my detached Entity has a lover version number that the one on the DB?

      1. Thanks, it’s getting more clear 😉 ,I already read that part of your book, I preserve detached entities on the browser and let the client exchange the with the server, I think it’s fine like this.

Leave a Reply

Your email address will not be published. Required fields are marked *