Hibernate Tips: Map 1 Entity Attribute to 2 Columns
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please leave a comment below.
Question:
I have a legacy database that I don’t want to change. It stores a date and a timestamp in 2 separate columns. How can I map it to 1 entity attribute?
Solution:
Normally, JPA and Hibernate map each entity attribute to one column in a database table. Mapping an attribute to 2 columns requires a small workaround, that uses 1 transient and 2 internal attributes. Your domain model uses the transient attribute, which gets mapped to the 2 internal attributes. Hibernate then maps the internal attributes to the database table.
Let’s take a look at an example.
Table and Entity Model
The review table stores the date and the time of the review in the columns postedAtDate and postedAtTime.
The Review entity maps these 2 columns to the postedAtDate and postedAtTime attributes. As you can see in the diagram, the Review class doesn’t provide any getter or setter methods for these attributes. So, they are not accessible from the outside. The postedAt attribute of the Review entity doesn’t get mapped to the database table but the class provides a getter and a setter method for it. This is the attribute that will be used by the business logic.
A Mapping in 3 Steps
You can implement such a mapping in 3 steps:
- You need to annotate your primary key attribute with the @Id annotation so that Hibernate uses field-based access. Hibernate then doesn’t use the getter and setter methods, which enables you to implement them in any way you want.
- You also need to annotate the postedAt attribute with @Transient so that Hibernate doesn’t map it to the database.
- The getPostedAt and setPostedAt methods need to also read and update the postedAtDate and postedAtTime attributes to make sure that they stay in sync with the postedAt attribute.
You can see the final mapping here:
@Entity public class Review { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String comment; private LocalDate postedAtDate; private LocalTime postedAtTime; @Transient private LocalDateTime postedAt; public Long getId() { return id; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public LocalDateTime getPostedAt() { if (postedAt == null) { this.postedAt = LocalDateTime.of(this.postedAtDate, this.postedAtTime); } return postedAt; } public void setPostedAt(LocalDateTime postedAt) { this.postedAt = postedAt; this.postedAtDate = postedAt.toLocalDate(); this.postedAtTime = postedAt.toLocalTime(); } }
Queries use Internal Attributes
Based on this mapping, Hibernate maps the internal postedAtDate and postedAtTime attributes to 2 database columns, and you don’t need to be aware of it as long as you don’t perform any queries. But Hibernate doesn’t map the postedAt attribute and you, therefore, can’t use it in a query. You need to use the two internal attributes instead.
TypedQuery<Review> q = em.createQuery("SELECT r FROM Review r WHERE r.postedAtDate = :date AND r.postedAtTime = :time", Review.class); q.setParameter("date", LocalDate.from(dateTime)); q.setParameter("time", LocalTime.from(dateTime)); Review r = q.getSingleResult();
Learn more:
If you want to learn more about advanced mappings, you might enjoy the following articles:
- JPA 2.1 Attribute Converter – The better way to persist enums
- How to map encrypted database columns with Hibernate’s @ColumnTransformer annotation
- Hibernate Tips: Calculate entity attributes with @Formula
Hibernate Tips Book
Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.
It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.