Site icon JVM Advent

How to map PostgreSQL Enums to JPA entity properties using Hibernate

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 type to a Java array 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>2.3.5</version>
</dependency>

If you’re using older versions of Hibernate, 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. 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 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()
);

Cool, 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.

Author: Vlad Mihalcea

I work as a Hibernate Developer Advocate, I wrote the High-Performance Java Persistence book and video course series. I speak at conferences and I created several open-source projects, like FlexyPool, hibernate-types, and db-util.

Exit mobile version