JPA AttributeConverter – A Beginner’s Guide

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 this article, we are going to see how the JPA AttributeConverter works and how we can use it to customize the entity attribute to the database column mapping.

For instance, we could use a JPA AttributeConverter to map a Java MonthDay to a database DATE column because Hibernate doesn’t offer a built-in MonthDayType to handle this particular mapping.

Domain Model

Our application uses the following annual_subscription database table:

JPA AttributeConverter database table

An annual subscription is needed to be renewed every year on a given day, so the payment_date column stores the day and the month when the payment needs to be made.

The annual_subscription table is mapped to the following AnnualSubscription JPA entity:

@Entity(name = "AnnualSubscription")
@Table(name = "annual_subscription")
public class AnnualSubscription {

    @Id
    private Long id;

    @Column(
        name = "price_in_cents"
    )
    private int priceInCents;

    @Column(
        name = "payment_day", 
        columnDefinition = "date"
    )
    @Convert(
        converter = MonthDayDateAttributeConverter.class
    )
    private MonthDay paymentDay;

    public Long getId() {
        return id;
    }

    public AnnualSubscription setId(
            Long id) {
        this.id = id;
        return this;
    }

    public int getPriceInCents() {
        return priceInCents;
    }

    public AnnualSubscription setPriceInCents(
            int priceInCents) {
        this.priceInCents = priceInCents;
        return this;
    }

    public MonthDay getPaymentDay() {
        return paymentDay;
    }

    public AnnualSubscription setPaymentDay(
            MonthDay paymentDay) {
        this.paymentDay = paymentDay;
        return this;
    }
}

The AnnualSubscription entity uses the Fluent-style API, which as you will soon see, it greatly simplifies the way we can build a JPA entity. For more details about using the FLuent-style API with JPA and Hibernate, check out this article.

The paymentDay entity attribute type is MonthDay. However, by default, Hibernate doesn’t support this Java type, so we need to provide a custom mapper.

Without providing a custom mapper, Hibernate will use the SerializableType for the paymentDay entity attribute and persist it as a byte[] array column type, which will not work for us since the payment_day column type is date.

So, we have two options. We can either use a Hibernate-specific custom Type or a JPA AttributeConverter to handle the mapping between the MonthDay Java type and the date column type.

JPA AttributeConverter

If you don’t need to provide a custom JDBC binding and fetching logic, then the JPA AttributeConverter is a viable solution to define the mapping between a given Java Object type and a database column type.

In our case, we need to create the following MonthDayDateAttributeConverter class that implements the JPA AttributeConverter interface:

public class MonthDayDateAttributeConverter
        implements AttributeConverter<MonthDay, java.sql.Date> {

    @Override
    public java.sql.Date convertToDatabaseColumn(
            MonthDay monthDay) {
        if (monthDay != null) {
            return java.sql.Date.valueOf(
                monthDay.atYear(1)
            );
        }
        return null;
    }

    @Override
    public MonthDay convertToEntityAttribute(
            java.sql.Date date) {
        if (date != null) {
            LocalDate localDate = date.toLocalDate();
            return MonthDay.of(
                localDate.getMonth(), 
                localDate.getDayOfMonth()
            );
        }
        return null;
    }
}

The convertToDatabaseColumn method is called by the JPA provider prior to executing an INSERT or UPDATE statement. The convertToDatabaseColumn method takes a single parameter, which is the entity attribute and returns the value that needs to be set on the associated table column.

In our case, the convertToDatabaseColumn method transforms the MonthDay entity attribute to a java.sql.Date which will be set in the payment_day DATE column. Notice that the year is set to a pre-defined value since we are not interested in this temporal field. Note that the supplied monthDay parameter can be null, hence we need to apply the transformation only for non-null MonthDay Object references.

The convertToEntityAttribute method is called by the JPA provider when fetching an entity from the database, via a find method or when executing a JPQL or Criteria API query. The convertToEntityAttribute method also takes a single parameter, which is the underlying table column value and returns the value that needs to be set on the associated entity attribute.

Our convertToEntityAttribute method implementation transforms the java.sql.Date column value to a MonthDay Java Object that will be set on the associated entity attribute. Note that the supplied date parameter can be null, hence we need to apply the transformation only for non-null DATE database column values.

Mapping the JPA AttributeConverter

To instruct the JPA provider to use a given AttributeConverter implementation, we can use the @Convert JPA annotation on the entity attribute that needs to be transformed upon reading from and writing to the database:

@Column(
    name = "payment_day", 
    columnDefinition = "date"
)
@Convert(
    converter = MonthDayDateAttributeConverter.class
)
private MonthDay paymentDay;

Auto-registering the JPA AttributeConverter

If you have multiple entities that use a given Java type that’s handled by the same JPA AttributeConverter, then you could auto-register the converter via the @Converter annotation on the AttributeConverter implementation, as illustrated by the following example:

@Converter(autoApply = true)
public static class MonthDayDateAttributeConverter
    implements AttributeConverter<MonthDay, java.sql.Date> {

    //Code omitted for brevity
}

Now, if you’re using Hibernate, you can define a MetadataBuilderContributor implementation that registers the MonthDayDateAttributeConverter, like this:

public class AttributeConverterMetadataBuilderContributor
    implements MetadataBuilderContributor {

    @Override
    public void contribute(
            MetadataBuilder metadataBuilder) {
        metadataBuilder.applyAttributeConverter(
            MonthDayDateAttributeConverter.class
        );
    }
}

To instruct Hibernate to use the AttributeConverterMetadataBuilderContributor when bootstrapping the EntityManagerFactory or SessionFactory, we need to use the hibernate.metadata_builder_contributor configuration property.

If you are using Spring Boot, you can define it in the application.properties file, like this:

hibernate.metadata_builder_contributor=com.vladmihalcea.book.hpjp.hibernate.type.AttributeConverterMetadataBuilderContributor

The hibernate.metadata_builder_contributor property can take the fully-qualified name of the class that implements a MetadataBuilderContributor.

Or, if you are using the JPA persistence.xml file, you can provide the hibernate.metadata_builder_contributor property in the properties XML tag:

<property 
    name="hibernate.metadata_builder_contributor"
    value="com.vladmihalcea.book.hpjp.hibernate.type.AttributeConverterMetadataBuilderContributor"
/>

Testing time

When persisting an AnnualSubscription entity:

entityManager.persist(
    new AnnualSubscription()
        .setId(1L)
        .setPriceInCents(700)
        .setPaymentDay(
            MonthDay.of(Month.AUGUST, 17)
        )
);

We can see that Hibernate generates the following SQL INSERT statement:

INSERT INTO annual_subscription (
    id,
    price_in_cents, 
    payment_day 
) 
VALUES 
(
    1,
    700, 
    '0001-08-17' 
)

And, when fetching the AnnualSubscription entity, we can see that the paymenentDay entity attribute is properly transformed from the DATE column value to a MonthDay Java Object:

AnnualSubscription subscription = entityManager.find(
    AnnualSubscription.class, 
    1L
);

assertEquals(
    MonthDay.of(Month.AUGUST, 17), 
    subscription.getPaymentDay()
);

That’s it!

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

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

Conclusion

The JPA AttributeConverter feature is very useful when we need to transform an entity attribute prior to persisting or fetching it from the database.

However, if you want more control over how the underlying JDBC PreparedStatement parameter is bound or how the ResultSet column values are fetched, then you need to use a Hibernate-specific custom Type, as explained in this article.

Transactions and Concurrency Control eBook

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.