How to map a PostgreSQL Enum ARRAY to a JPA entity property using 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

The Hypersistence Utils OSS project allows you to map JSON, ARRAY, YearMonth, Month or database-specific columns (e.g., INET addresses).

In this article, we are going to see how you can map a PostgreSQL Enum ARRAY type to a Java array entity property when using JPA and Hibernate.

Maven dependency

First of all, you need to set up the following Maven dependency in your project pom.xml configuration file:

<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-55</artifactId>
    <version>${hypersistence-utils.version}</version>
</dependency>

If you’re using older versions of Hibernate (e.g., 5.4, 5,3, 5.2, or 5.1), then check out the Hypersistence Utils GitHub repository for more info about the matching dependency for your current Hibernate version.

Domain Model

Let’s assume we have the following sensor_state PostgreSQL enum in our database schema:

CREATE TYPE sensor_state AS ENUM (
    'ONLINE',
    'OFFLINE',
    'UNKNOWN'
);

Our application needs to store Events in the following database table:

CREATE TABLE event (
  id bigint NOT NULL,
  sensor_names text[],
  sensor_values integer[],
  sensor_states sensor_state[],
  CONSTRAINT event_pkey PRIMARY KEY (id)
)

Notice that the sensor_names, sensor_values and sensor_states columns are stored as arrays.

Now, we want to map the event database table to the following Event JPA entity:

Event entity using the PostgreSQL Enum Array type

To map the PostgreSQL array column types to Java arrays, you need a custom Hibernate type since the built-in types don’t support persisting database-specific arrays.

However, thanks to the Hypersistence Utils library, you can easily map the event table to the following Event entity.

For Hibernate 6, the mapping will look as follows:

@Entity(name = "Event")
@Table(name = "event")
public class Event {

    @Id
    private Long id;

    @Type(StringArrayType.class)
    @Column(
        name = "sensor_names",
        columnDefinition = "text[]"
    )
    private String[] sensorNames;

    @Type(IntArrayType.class)
    @Column(
        name = "sensor_values",
        columnDefinition = "integer[]"
    )
    private int[] sensorValues;

    @Type(
        value = EnumArrayType.class,
        parameters = @Parameter(
            name = AbstractArrayType.SQL_ARRAY_TYPE, 
            value = "sensor_state"
        )
    )
    @Column(
        name = "sensor_states",
        columnDefinition = "sensor_state[]"
    )
    private SensorState[] sensorStates;
}

And for Hibernate 5, like this:

@Entity(name = "Event")
@Table(name = "event")
@TypeDef(
    typeClass = StringArrayType.class,
    defaultForType = String[].class
)
@TypeDef(
    typeClass = IntArrayType.class,
    defaultForType = int[].class
)
@TypeDef(
    typeClass = EnumArrayType.class,
    defaultForType = SensorState[].class,
    parameters = {
        @Parameter(
            name = AbstractArrayType.SQL_ARRAY_TYPE,
            value = "sensor_state"
        )
    }
)
public class Event {

    @Id
    private Long id;

    @Column(
        name = "sensor_names",
        columnDefinition = "text[]"
    )
    private String[] sensorNames;

    @Column(
        name = "sensor_values",
        columnDefinition = "integer[]"
    )
    private int[] sensorValues;

    @Column(
        name = "sensor_states",
        columnDefinition = "sensor_state[]"
    )
    private SensorState[] sensorStates;
}

Notice the Fluent-style API used by the Event entity. While JPA is more strict when it comes to defining setters, Hibernate allows you to define the setters so that you can build the entity using a Fluent-style API. For more details, check out this article.

The @TypeDef annotation is used to define the mapping between the Java array class types and their associated Hibernate types:

  • The Java String[] array type is handled by the StringArrayType.
  • The Java int[] array type is handled by the IntArrayType
  • The Java SensorState[] is handled by the EnumArrayType. The AbstractArrayType.SQL_ARRAY_TYPE parameter is used to describe the database-specific column type used for storing the Enum.

The SensorState Java enum is mapped as follows:

public enum SensorState {
    ONLINE, 
    OFFLINE, 
    UNKNOWN;
}

Testing Time

Now, when storing the following Event entity:

entityManager.persist(
    new Event()
    .setId(1L)
    .setSensorNames(
        new String[]{
            "Temperature", 
            "Pressure"
        })
    .setSensorValues(
        new int[]{
            12, 
            756
        }
    )
    .setSensorStates(
        new SensorState[]{
            SensorState.ONLINE, 
            SensorState.OFFLINE,
            SensorState.ONLINE,     
            SensorState.UNKNOWN
        }
    )   
);

Hibernate executes the following SQL INSERT statement:

Query:["
    insert into event (
        sensor_names, 
        sensor_states, 
        sensor_values, 
        id
    ) 
    values (
        ?, 
        ?, 
        ?, 
        ?
    )
"], 
Params:[(
    {"Temperature","Pressure"}, 
    {"ONLINE","OFFLINE","ONLINE","UNKNOWN"}, 
    {"12","756"}, 
    1
)]

And, when we fetch the Event entity, we can see that all properties are fetched properly

Event event = entityManager.find(Event.class, 1L);

assertArrayEquals(
    new String[]{
        "Temperature", 
        "Pressure"
    }, 
    event.getSensorNames()
);

assertArrayEquals(
    new int[]{
        12, 
        756
    }, 
    event.getSensorValues()
);

assertArrayEquals(
    new SensorState[]{
        SensorState.ONLINE, 
        SensorState.OFFLINE, 
        SensorState.ONLINE, 
        SensorState.UNKNOWN
    }, 
    event.getSensorStates()
);

Awesome, right?

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 Hypersistence Utils project supports more than ARRAY types.

You can map PostgreSQL-specific Enums, nullable Character, JSON, or even provide your own immutable Hibernate custom Types.

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.