How to map a PostgreSQL Enum ARRAY to a JPA entity property using Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

The open-source hibernate-types 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>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

If you’re using older versions of Hibernate (e.g. 5.1 or 4.3), check out the hibernate-types 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 hibernate-types library you can easily map the event table to the following Event entity:

@Entity(name = "Event")
@Table(name = "event")
@TypeDefs({
    @TypeDef(
        typeClass = StringArrayType.class,
        defaultForType = String[].class
    ),
    @TypeDef(
        typeClass = IntArrayType.class,
        defaultForType = int[].class
    ),
    @TypeDef(
        typeClass = EnumArrayType.class,
        defaultForType = SensorState[].class,
        parameters = {
            @Parameter(
                name = EnumArrayType.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;

    public Long getId() {
        return id;
    }

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

    public String[] getSensorNames() {
        return sensorNames;
    }

    public Event setSensorNames(
            String[] sensorNames) {
        this.sensorNames = sensorNames;
        return this;
    }

    public int[] getSensorValues() {
        return sensorValues;
    }

    public Event setSensorValues(
            int[] sensorValues) {
        this.sensorValues = sensorValues;
        return this;
    }

    public SensorState[] getSensorStates() {
        return sensorStates;
    }

    public Event setSensorStates(
            SensorState[] sensorStates) {
        this.sensorStates = sensorStates;
        return this;
    }
}

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 EnumArrayType.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?

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

Conclusion

The hibernate-types project supports more than ARRAY types. You can map PostgreSQL-specific Enums, nullable Character, JSON, or even provide your own immutable Hibernate custom Types.

For more details about the hibernate-types project, check out this article.

FREE EBOOK

4 Comments on “How to map a PostgreSQL Enum ARRAY to a JPA entity property using Hibernate

  1. Can’t JPA Hibernate create PostgresSQL enum type when hbm2ddlauto=’create-drop’ or ‘update’?

    Should we always create enum types in the database to use it in JPA?

    • Using hbm2ddl in update mode is a terrible idea if you are using that in production.

      If you want this feature, you should open a Jira issue and provide a Pull Request with the fix. Otherwise, I doubt it will be implemented any time soon.

  2. Hi Vlad!
    Is it possible to map the SQL enum array not only to Java array but also to Set in the entity?

    For example:

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

    Thanks in advance,
    Sergei.

    • Yes, it is possible. But you need to provide a custom Hibernate Type to do that. You can use the existing EnumArrayType as the starting point to create an EnumSetType. Send me a Pull Request once you are done and don’t forget to create a test case that proves the type is working.

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.