How to map a Java multidimensional array with PostgreSQL and Hibernate

Viewed 1322

I'm trying to map a three-dimensional array from PostgreSQL with Hibernate. A need to store weights of a multilayer neural network.

How should I do it better?

All source I've met was too old. It seems to me that there are more convenient ways to cope with it.

UPD: I need Double[][][]

2 Answers

Hibernate Types project

The hibernate-types project allows you to persist multidimensional arrays for your JPA and Hibernate entity attributes.

Database table

So, assuming you have the following plane database table:

CREATE TABLE plane (
    id INT8 NOT NULL,
    name VARCHAR(255),
    seat_grid seat_status[][],
    PRIMARY KEY (id)
)

Where the seat_status is a PostgreSQL enum:

CREATE TYPE seat_status
AS ENUM (
    'UNRESERVED',
    'RESERVED',
    'BLOCKED'
);

JPA entity

You can map the plane database table as follows:

@Entity(name = "Plane")
@Table(name = "plane")
@TypeDef(
    name = "seat_status_array",
    typeClass = EnumArrayType.class
)
public static class Plane {
 
    @Id
    private Long id;
 
    private String name;
 
    @Type(
        type = "seat_status_array",
        parameters = @org.hibernate.annotations.Parameter(
            name = "sql_array_type",
            value = "seat_status"
        )
    )
    @Column(
        name = "seat_grid",
        columnDefinition = "seat_status[][]"
    )
    private SeatStatus[][] seatGrid;
 
    //Getters and setters omitted for brevity

    public SeatStatus getSeatStatus(int row, char letter) {
        return seatGrid[row - 1][letter - 65];
    }
}

So, you need to declare the appropriate Hibernate Type to use. For enums, you need to use the EnumArrayType:

@TypeDef(
    name = "seat_status_array",
    typeClass = EnumArrayType.class
)

The @Type annotation allows you to pass parameters to the Hibernate Type, like the SQL array class:

@Type(
    type = "seat_status_array",
    parameters = @org.hibernate.annotations.Parameter(
        name = "sql_array_type",
        value = "seat_status"
    )
)

Testing time

Now, when you persist the following Post entity:

entityManager.persist(
    new Plane()
        .setId(1L)
        .setName("ATR-42")
        .setSeatGrid(
            new SeatStatus[][] {
                {
                    SeatStatus.BLOCKED, SeatStatus.BLOCKED,
                    SeatStatus.BLOCKED, SeatStatus.BLOCKED
                },
                {
                    SeatStatus.UNRESERVED, SeatStatus.UNRESERVED,
                    SeatStatus.RESERVED, SeatStatus.UNRESERVED
                },
                {
                    SeatStatus.RESERVED, SeatStatus.RESERVED,
                    SeatStatus.RESERVED, SeatStatus.RESERVED
                }
            }
        )
);

Hibernate will issue the proper SQL INSERT statement:

INSERT INTO plane (
    name,
    seat_grid,
    id
)
VALUES (
    'ATR-42',
    {
        {"BLOCKED", "BLOCKED", "BLOCKED", "BLOCKED"},
        {"UNRESERVED", "UNRESERVED", "RESERVED", "UNRESERVED"},
        {"RESERVED", "RESERVED", "RESERVED", "RESERVED"}
    },
    1
)

And, when fetching the entity, everything works as expected:

Plane plane = entityManager.find(Plane.class, 1L);

assertEquals("ATR-42", plane.getName());
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'A'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'B'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'C'));
assertEquals(SeatStatus.BLOCKED, plane.getSeatStatus(1, 'D'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'A'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'B'));
assertEquals(SeatStatus.RESERVED, plane.getSeatStatus(2, 'C'));
assertEquals(SeatStatus.UNRESERVED, plane.getSeatStatus(2, 'D'));
Related