DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • How To Backup and Restore a PostgreSQL Database
  • Batch Request Processing With API Gateway
  • Observability Architecture: Financial Payments Introduction
  • Execution Type Models in Node.js
  1. DZone
  2. Data Engineering
  3. Databases
  4. Mapping Enums Done Right With @Convert in JPA 2.1

Mapping Enums Done Right With @Convert in JPA 2.1

Tomasz Nurkiewicz user avatar by
Tomasz Nurkiewicz
CORE ·
Jun. 06, 13 · Interview
Like (6)
Save
Tweet
Share
67.04K Views

Join the DZone community and get the full member experience.

Join For Free

If you ever worked with Java enums in JPA you are definitely aware of their limitations and traps. Usingenum as a property of your @Entity is often very good choice, however JPA prior to 2.1 didn’t handle them very well. It gave you 2+1 choices:

  1. @Enumerated(EnumType.ORDINAL) (default) will map enum values using Enum.ordinal(). Basically first enumerated value will be mapped to 0 in database column, second to 1, etc. This is very compact and works great to the point when you want to modify your enum. Removing or adding value in the middle or rearranging them will totally break existing records. Ouch! To make matters worse, unit and integration tests often work on clean database, so they won’t catch discrepancy in old data.
  2. @Enumerated(EnumType.STRING) is much safer because it stores string representation of enum. You can now safely add new values and move them around. However renaming enum in Java code will still break existing records in DB. Even more important, such representation is very verbose, unnecessarily consuming database resources.
  3. You can also use raw representation (e.g. single char or int) and map it manually back and forth in @PostLoad/@PrePersist/@PreUpdate events. Most flexible and safe from database perspective, but quite ugly.

Luckily Java Persistence API 2.1 (JSR-388) released few days ago provides standardized mechanism of pluggable data converters. Such API was present for ages in proprietary forms and it’s not really rocket science, but having it as part of JPA is a big improvement. To my knowledge Eclipselink is the only JPA 2.1 implementation available to date, so we will use it to experiment a bit.

We will start from sample Spring application developed as part of “Poor man’s CRUD: jqGrid, REST, AJAX, and Spring MVC in one house” article. That version had no persistence, so we will add thin DAO layer on top of Spring Data JPA backed by Eclipselink. Only entity so far is Book:

@Entity
public class Book {
 
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Integer id;
 
    //...
 
    private Cover cover;
 
    //...
}

Where Cover is an enum:

public enum Cover {
 
    PAPERBACK, HARDCOVER, DUST_JACKET
 
}

Neither ORDINAL nor STRING is a good choice here. The former because rearranging first three values in any way will break loading of existing records. The latter is too verbose. Here is where custom converters in JPA come into play:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
 
@Converter
public class CoverConverter implements AttributeConverter<Cover, String> {
 
    @Override
    public String convertToDatabaseColumn(Cover attribute) {
        switch (attribute) {
            case DUST_JACKET:
                return "D";
            case HARDCOVER:
                return "H";
            case PAPERBACK:
                return "P";
            default:
                throw new IllegalArgumentException("Unknown" + attribute);
        }
    }
 
    @Override
    public Cover convertToEntityAttribute(String dbData) {
        switch (dbData) {
            case "D":
                return DUST_JACKET;
            case "H":
                return HARDCOVER;
            case "P":
                return PAPERBACK;
            default:
                throw new IllegalArgumentException("Unknown" + dbData);
        }
    }
}

OK, I won’t insult you, my dear reader, explaining this. Converting enum to whatever will be stored in relational database and vice-versa. Theoretically JPA provider should apply converters automatically if they are declared with:

@Converter(autoApply = true)

It didn’t work for me. Moreover declaring them explicitly instead of @Enumerated in@Entity class didn’t work as well:

import javax.persistence.Convert;
 
//...
 
@Convert(converter = CoverConverter.class)
private Cover cover;

Resulting in exception:

Exception Description: The converter class [com.blogspot.nurkiewicz.CoverConverter] 
specified on the mapping attribute [cover] from the class [com.blogspot.nurkiewicz.Book] was not found. 
Please ensure the converter class name is correct and exists with the persistence unit definition.

Bug or feature, I had to mention converter in orm.xml:

<?xml version="1.0"?>
<entity-mappings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm" version="2.1">
    <converter class="com.blogspot.nurkiewicz.CoverConverter"/>
</entity-mappings>

And it flies! I have a freedom of modifying my Cover enum (adding, rearranging, renaming) without affecting existing records.

One tip I would like to share with you is related to maintainability. Every time you have a piece of code mapping from or to enum, make sure it’s tested properly. And I don’t mean testing every possible existing value manually. I am more after a test making sure that newenum values are reflected in mapping code. Hint: code below will fail (by throwingIllegalArgumentException) if you add new enum value but forget to add mapping code from it:

for (Cover cover : Cover.values()) {
    new CoverConverter().convertToDatabaseColumn(cover);
}

Custom converters in JPA 2.1 are much more useful than what we saw. If you combine JPA with Scala, you can use @Converter to map database columns directly toscala.math.BigDecimal, scala.Option or small case class. In Java there will finally be a portable way of mapping Joda time. Last but not least, if you like (very) strongly typed domain, you may wish to have PhoneNumber class (with isInternational(),getCountryCode() and custom validation logic) instead of String or long. This small addition in JPA 2.1 will surely improve domain objects quality significantly.

If you wish to play a bit with this feature, sample Spring web application is available on GitHub.

Database Relational database

Published at DZone with permission of Tomasz Nurkiewicz, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • How To Backup and Restore a PostgreSQL Database
  • Batch Request Processing With API Gateway
  • Observability Architecture: Financial Payments Introduction
  • Execution Type Models in Node.js

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: