How to use @PrePersist and @PreUpdate on Embeddable with JPA and Hibernate

(Last Updated On: May 2, 2018)
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

In a previous article, I explained how you could audit entity modifications using the JPA @EntityListeners for embeddable types.

Since Hibernate ORM 5.2.17 now allows you to use the @PrePersist and @PreUpdate JPA entity listeners, we can simplify the previous example, as you will see in this article.

Domain Model

Assuming we have the following Domain Model classes:

We want to encapsulate the audit logic in the Audit embeddable type:

@Embeddable
public class Audit {

    @Column(name = "created_on")
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;
    
    @Column(name = "updated_on")
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    @PrePersist
    public void prePersist() {
        createdOn = LocalDateTime.now();
        createdBy = LoggedUser.get();
    }

    @PreUpdate
    public void preUpdate() {
        updatedOn = LocalDateTime.now();
        updatedBy = LoggedUser.get();
    }

    //Getters and setters omitted for brevity
}

Notice the prePersist and preUpdate methods which are annotated with the JPA entity event listeners.

The JPA entities will use the Audit embeddable type as follows:

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

    @Id
    private Long id;

    private String title;
    
    @Embedded
    private Audit audit = new Audit();

    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(name = "post_id"),
        inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<Tag> tags = new ArrayList<>();

    //Getters and setters omitted for brevity
}

@Entity(name = "Tag")
@Table(name = "tag")
public class Tag {

    @Id
    private String name;

    @Embedded
    private Audit audit = new Audit();

    //Getters and setters omitted for brevity
}

Testing time

Now, when inserting 3 Tag entities:

Tag jdbc = new Tag();
jdbc.setName("JDBC");
entityManager.persist(jdbc);

Tag hibernate = new Tag();
hibernate.setName("Hibernate");
entityManager.persist(hibernate);

Tag jOOQ = new Tag();
jOOQ.setName("jOOQ");
entityManager.persist(jOOQ);

Hibernate properly sets the created_on and created_by columns on the associated tag rows:

INSERT INTO tag (
    created_by, 
    created_on, 
    updated_by, 
    updated_on, 
    name
) 
VALUES (
    'Alice', 
    '2018-05-02 09:56:54.939', 
    NULL(VARCHAR), 
    NULL(TIMESTAMP), 
    'JDBC'
)

INSERT INTO tag (
    created_by, 
    created_on, 
    updated_by, 
    updated_on, 
    name
) 
VALUES (
    'Alice', 
    '2018-05-02 09:56:54.955', 
    NULL(VARCHAR), 
    NULL(TIMESTAMP), 
    'Hibernate'
)

INSERT INTO tag (
    created_by, 
    created_on, 
    updated_by, 
    updated_on, 
    name
) 
VALUES (
    'Alice', 
    '2018-05-02 09:56:54.955', 
    NULL(VARCHAR), 
    NULL(TIMESTAMP), 
    'jOOQ'
)

The same goes for the Post entity:

Post post = new Post();
post.setId(1L);
post.setTitle(
    "High-Performance Java Persistence, 1st Edition
");

post.getTags().add(
    entityManager.find(Tag.class, "JDBC")
);
post.getTags().add(
    entityManager.find(Tag.class, "Hibernate")
);
post.getTags().add(
    entityManager.find(Tag.class, "jOOQ")
);

entityManager.persist(post);

Hibernate generating the following INSERT statements:

INSERT INTO post (
    created_by, 
    created_on, 
    updated_by, 
    updated_on, 
    title, 
    id
) 
VALUES (
    'Alice', 
    '2018-05-02 09:56:55.046', 
    NULL(VARCHAR), 
    NULL(TIMESTAMP), 
    'High-Performance Java Persistence, 1st Edition', 
    1
)

INSERT INTO post_tag (post_id, tag_id) VALUES (1, 'JDBC')
INSERT INTO post_tag (post_id, tag_id) VALUES (1, 'Hibernate')
INSERT INTO post_tag (post_id, tag_id) VALUES (1, 'jOOQ')

When updating the Post entity:

Post post = entityManager.find(Post.class, 1L);

post.setTitle(
    "High-Performance Java Persistence, 2nd Edition"
);

The updated_on and update_by columns will be set by the @PreUpdate event listener on the embeddable type:

UPDATE 
    post 
SET 
    created_by = 'Alice', 
    created_on = '2018-05-02 09:56:55.046', 
    updated_by = 'Alice', 
    updated_on = '2018-05-02 09:56:55.106', 
    title = 'High-Performance Java Persistence, 2nd Edition' 
WHERE 
    id = 1

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

So, while previously, you could achieve the same goal using an @EntityListener, now you apply the @PrePersist and @PreUpdate event listeners son the embeddable type, therefore simplifying the implementation.

Download free ebook sample

Newsletter logo
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.

10 Comments on “How to use @PrePersist and @PreUpdate on Embeddable with JPA and Hibernate

  1. What happens if entity itself has @PerPresist and @PerUpdate too?

  2. Hi,

    My requirement is bit different when i’m trying to update or delete elements from the DB using JPA entity manager,I need to capture modified columns of the current entity and previous/orginal state of that column and save those modified columns values into separate Audit table without hitting DB.Can we achieve this using JPA @prepersist feature?
    Thanks in advance.

    • No. You are better off using Envers or use a custom FlushEventListener.

  3. That may be, but you will go down in history as the first person that doesn’t provide a link to their source code.

    • I don’t want to deprive my readers from discovering all the wonders that my High-Performance Java Persistence GitHub repository has to offer. They always come to thank me for that.

  4. Your articles are very interesting. Links in the article to the specific location in your github repository that has the full source code discussed in the article would be very helpful.

  5. I was also unable to get this example to work due to nulls being placed in the audit fields too. Someone else asked this question and you referenced stack overflow, but I was unable to find the question there. Any ideas on why the example is behaving this way?

    Thanks. I like your posts and just bought your book because of them.

    Cheers,
    Steve

    • This example is also on my High-Performance Java Persistence GitHub repository and works just fine. Try to see why mine works and yours does not.

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.

Want to run your data access layer at warp speed?