Reputation: 305
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[][][]
Upvotes: 4
Views: 2024
Reputation: 153730
The hibernate-types project allows you to persist multidimensional arrays for your JPA and Hibernate entity attributes.
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'
);
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"
)
)
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'));
Upvotes: 3
Reputation: 2088
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;
}
}
Upvotes: 2