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

(Last Updated On: January 29, 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 and Video Courses 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

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

  1. Lately i have seen some JPQL update statements. Basically, they look like the following:
    update User e SET e.group.id = :newGroupId where e.id = :userId

    User {
    @Id
    private String id;

    @ManyToOne
    @JoinColumn(name = “group_id”)
    private Group group;

    }

    Group {
    @Id
    private String id;

    }

    In my opinion, what this statement means is “update the id property of the group entity which is currently assigned to a given user”

    Unfortunately, what it does “it updates the foreign key which links user to group” … generated SQL looks like:
    update user e set e.group_id = :newGroupId where e.id = :userId

    That SQL statement is exactly what i expect when the JPQL is something like:
    update User e SET e.group = :newGroup where e.id = :userId

    I don’t know, maybe i’m wrong. What’s your opinion on that? Is the initial statement legal?

    1. The JPQL is correct. When you reference and association, you are actually referencing the FK. More, the PK is meant to be immutable.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.