如何将 Java 中的二维矩阵映射到 Hibernate/JPA?

发布于 2024-10-01 04:06:29 字数 824 浏览 0 评论 0 原文

我有一个遗留数据库,我正在尝试重新设计以适应 21 世纪。现有的数据结构之一涉及包含二维值矩阵的特定类。如果我要从数据库对此类进行逆向工程,我最终会得到一系列属性,例如:

private BigDecimal NODE_1_MATRIX_POS_1_1;
private BigDecimal NODE_1_MATRIX_POS_1_2;

等等。由于这是一个 6x6 矩阵,因此有很多这样的列。

我一直在寻找更好的方法,但我不确定我是否在那里。我想做的是这样的:

@Entity
public class TestClass {

    @Id
    private long id;

    @CollectionOfElements
    @JoinTable(
        name="MATRIX_DATA", 
        joinColumns=@JoinColumn(name="ENTRY_ID"))
    private List<List<BigDecimal>> matrix;

但这失败了:

org.hibernate.MappingException: Could not determine type for: java.util.List, at table: MATRIX_DATA, for columns: [org.hibernate.mapping.Column(element)]

我想我应该四处询问并尝试找到正确的方法来解决此映射,而不是仅仅尝试修复错误挑战。有人对通过 JPA 映射多维数组感到成功和满意吗?

I have a legacy database I'm trying to redesign into the 21st century. One of the existing data structures involves a particular class which contains a 2-dimensional matrix of values. If I were to reverse-engineer this class from the database, I'd end up with a series of attributes like:

private BigDecimal NODE_1_MATRIX_POS_1_1;
private BigDecimal NODE_1_MATRIX_POS_1_2;

and so on. Since this is a 6x6 matrix, there are a lot of such columns.

I've been looking for a better way, but I'm not sure I'm there. What I'd like to do is something like this:

@Entity
public class TestClass {

    @Id
    private long id;

    @CollectionOfElements
    @JoinTable(
        name="MATRIX_DATA", 
        joinColumns=@JoinColumn(name="ENTRY_ID"))
    private List<List<BigDecimal>> matrix;

But this fails:

org.hibernate.MappingException: Could not determine type for: java.util.List, at table: MATRIX_DATA, for columns: [org.hibernate.mapping.Column(element)]

Rather than just trying to fix the error, I thought I'd ask around and try to find the right approach to solving this mapping challenge. Has anyone found success and satisfaction mapping multidimensional arrays via JPA?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

抱猫软卧 2024-10-08 04:06:29

我认为我应该四处询问并尝试找到解决此映射挑战的正确方法,而不是仅仅尝试修复错误。有人对通过 JPA 映射多维数组感到成功和满意吗?

AFAIK,标准 JPA 不支持嵌套集合。 JPA wiki 书中有关于这个主题的很好的部分(我只引用了其中的一部分):

嵌套集合、映射和矩阵

在对象中有些常见
模型具有复杂的集合
关系,例如 List
List(即矩阵),或 Map
Map,或 ListMap,等等
在。不幸的是这些类型
集合很难映射到
关系数据库。

JPA不支持嵌套集合关系,通常情况下
最好改变你的对象模型
避免它们 坚持不懈
查询更方便。一种解决方案是
创建一个包裹嵌套对象的对象
收藏。

例如,如果员工
由 a 键入的 ProjectMap
String 项目类型和值 a
列表项目。为了映射这个
新的 ProjectType 类可能是
创建用于存储项目类型和
OneToManyProject

...

这就是我的建议。例如:

@Entity
public class TestClass {    
    @Id
    private long id;

    @OneToMany(mappedBy="testClass")
    private List<MatrixRow> matrix;
}

MatrixLine 的位置(省略许多细节):

@Entity
public class MatrixRow {
    @Id
    private long id;

    @ManyToOne
    private TestClass testClass;

    @CollectionOfElements
    private List<BigDecimal> row;
}

或者也许您可以使用自定义用户类型(我不太确定这将如何工作)。

或者(毕竟,您已经在使用不可移植注释)看看这个问题,看看如何扩展 Hibernate:

Rather than just trying to fix the error, I thought I'd ask around and try to find the right approach to solving this mapping challenge. Has anyone found success and satisfaction mapping multidimensional arrays via JPA?

AFAIK, nested collections are not supported by standard JPA. The JPA wiki book has a good section on this topic (I'm quoting only a part of it):

Nested Collections, Maps and Matrices

It is somewhat common in an object
model to have complex collection
relationships such as a List of
Lists (i.e. a matrix), or a Map of
Maps, or a Map of Lists, and so
on. Unfortunately these types of
collections map very poorly to a
relational database.

JPA does not support nested collection relationships, and normally
it is best to change your object model
to avoid them to make persistence and
querying easier. One solution is to
create an object that wraps the nested
collection.

For example if an Employee had a
Map of Projects keyed by a
String project-type and the value a
List or Projects. To map this a
new ProjectType class could be
created to store the project-type and
a OneToMany to Project.

...

And that would be my suggestion. For example:

@Entity
public class TestClass {    
    @Id
    private long id;

    @OneToMany(mappedBy="testClass")
    private List<MatrixRow> matrix;
}

Where MatrixLine would be (omitting many details):

@Entity
public class MatrixRow {
    @Id
    private long id;

    @ManyToOne
    private TestClass testClass;

    @CollectionOfElements
    private List<BigDecimal> row;
}

Or maybe you could use a custom user type (I'm not too sure how this would work).

Or (after all, you're already using non portable annotations) have a look at this question to see how you could extend Hibernate:

甜是你 2024-10-08 04:06:29

Hypersistence Utils 项目

您可以使用 Hypersistence Utils 项目映射 PostgreSQL 多维数组。

您可以选择在实体属性端使用Java数组或使用List

数据库表

例如,假设您有以下 plane 数据库表:

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

其中 seat_status 是 PostgreSQL 枚举:

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

JPA 实体

您可以映射 seatGrid使用 EnumArrayType 的列:

@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];
    }
}

因此,您需要声明要使用的适当的 Hibernate 类型。对于枚举,您需要使用 EnumArrayType

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

@Type 注释允许您将参数传递给 Hibernate 类型,就像 SQL 数组类一样:

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

测试时间

现在,当您持久化以下 Post 实体:

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 将发出正确的 SQL INSERT 语句:

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

并且,在获取实体时,一切都会按预期工作:

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'));

Hypersistence Utils project

You can map a PostgreSQL multidimensional array using the Hypersistence Utils project.

You can choose to use a Java array on the entity attribute side or use List.

Database table

For example, 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 seatGrid column using the EnumArrayType:

@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'));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文