How to use @PrePersist and @PreUpdate on Embeddable with JPA and Hibernate
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
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?
I'm running an online workshop on the 11th of October about High-Performance SQL.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.
