4

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[][][]

4
  • What model do you want to represent in the DB exactly? Multi-dimensional array is a loose concept, i.e. Java has an array-of-arrays concept, while PostgreSQL's array must be "rectangular". -- Hibernate does not support array types out-of-the-box. Commented May 29, 2017 at 13:46
  • 1
    @pozs I need Double[][][] Commented May 29, 2017 at 13:48
  • we'll need slightly more information. i.e. will it always be rectangular, or do you use that fact that in Java, you have array-of-arrays? Some example data (a few rows) and how you are going to use the elements' value, etc. Commented May 29, 2017 at 13:51
  • this could be useful , stackoverflow.com/questions/1647583/… Commented May 29, 2017 at 14:42

2 Answers 2

3

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'));
Sign up to request clarification or add additional context in comments.

Comments

2

You can write your own UserType. It's postgresql specific though. I wrote one for two dimensional array (int[][]), and I'm sure you can do same for 3-di array.

Here is my code:

package kg.nsi.domain.customtypes;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.sql.*;

/**
 * Created by erlan on 12/22/17.
 */

public class BiDirectionalIntArrayUserType implements UserType {
    private final int[] arrayTypes = new int[]{Types.ARRAY};

    @Override
    public int[] sqlTypes() {
        return arrayTypes;
    }

    @Override
    public Class<int[][]> returnedClass() {
        return int[][].class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return x == null ? y == null : x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x == null ? 0 : x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        if (names != null && names.length > 0 && rs != null && rs.getArray(names[0]) != null) {
            Object array = rs.getArray(names[0]).getArray();
            if (array instanceof int[][])
                return array;
            else
                return convertShortArrayToInt((Number[][]) array);
        }

        return null;
    }

    private int[][] convertShortArrayToInt(Number[][] array) {
        int[][] intArray = new int[array.length][array[0].length];
        for (int i = 0; i < array.length; i++)
            for(int j = 0; j < array[0].length; j++)
                intArray[i][j] = array[i][j].intValue();

        return intArray;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if (value != null && st != null) {
            int[][] intarray = (int[][]) value;
            Array array = session.connection().createArrayOf("integer", intarray);
            st.setArray(index, array);
        } else {
            st.setNull(index, arrayTypes[0]);
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if (value == null)
            return null;

        int[][] intarray = (int[][]) value;
        int[][] clone = new int[intarray.length][intarray[0].length];
        for (int i = 0; i < intarray.length; i++)
            for(int j = 0; j < intarray[0].length; j++)
                clone[i][j] = intarray[i][j];

        return clone;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.