The best way to map a Java 1.8 Optional entity attribute 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
StackOverflow is a neverending source of great questions. This time, we are covering this question about using Java 1.8 Optional
with JPA and Hibernate.
Java 1.8 introduced the java.util.Optional
container object that may or may not contain a certain value. Combining Optional
and streams is very handy. Therefore, you might want that some nullable entity attributes be exposed as Optional
.
This article is going to demonstrate what are caveats of using Optional
with entity attributes, and how you can overcome them.
Domain Model
Let’s assume we have the following entity model:
The Post
entity is the root in our entity aggregate. There can be multiple PostComment(s)
associated with a Post
, and each PostComment
can have an Attachment
. Because the Attachment
is not mandatory, it makes sense to use an Optional
container for it.
Optional and Serializable
The java.util.Optional
does not implement the Serializable
interface. For this reason, we should never map an entity attribute as Optional
because that will restrict the entity usage.
For instance, we could have a detached instance stored in the HttpSession
because we are operating a long conversation workflow. Every object stored in the HttpSession
should be Serializable
because the session may be clustered on several web nodes.
If you’re using Java EE and Stateful Session Beans, then you must ensure that all entities are `Serializable, as otherwise the passivization process would fail.
For all these reasons, an entity attribute should not be mapped as a
java.util.Optional
.
All is not lost
But just because we cannot map an entity attribute as Optional
, it does not mean we cannot expose it using an Optional
container. If we are using field-based access persistence, then the underlying entity attribute can be mapped using the actual persisted type, while the getter method can use an Optional
instead.
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment implements Serializable { @Id @GeneratedValue private Long id; private String review; @ManyToOne(fetch = FetchType.LAZY) private Post post; @ManyToOne(fetch = FetchType.LAZY) private Attachment attachment; public Optional<Attachment> getAttachment() { return Optional.ofNullable(attachment); } public void setAttachment(Attachment attachment) { this.attachment = attachment; } //Other getters and setters omitted for brevity }
That’s it!
If you’re using property-based access, then the getter must expose the actual persisted type, in which case you need to have a separate
@Transient
method that uses theOptional
method return type.
Testing time
Assuming we have the following entities:
byte[] coverContent = new byte[] {1, 2, 3}; Post post = new Post(); post.setId(1L); post.setTitle("High-Performance Java Persistence"); entityManager.persist(post); PostComment comment1 = new PostComment(); comment1.setPost(post); entityManager.persist(comment1); Attachment cover = new Attachment(); cover.setContent(coverContent); entityManager.persist(cover); PostComment comment2 = new PostComment(); comment2.setPost(post); comment2.setAttachment(cover); entityManager.persist(comment2);
If we alaready have a list of PostComment(s)
:
List<PostComment> comments = entityManager.createQuery( "select pc " + "from PostComment pc " + "join pc.post p " + "where p.id = :postId", PostComment.class) .setParameter("postId", 1L) .getResultList();
We can process Attachment(s)
as follows:
Attachment notAvailable = getNotAvaillableImage(); List<Attachment> attachments = comments .stream() .map(pc -> pc.getAttachment() .orElse(notAvailable)) .collect(Collectors.toList());
If there is not Attachment
already set, we can use a default N/A image.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
When using JPA and Hibernate, you can make use of the Java 1.8 Optional
in your Domain Model entities. However, you have to make sure not to use it as a persistent property type.
