简体   繁体   English

JPA:@OneToMany 映射和复合主键 - 找不到逻辑列错误

[英]JPA: @OneToMany mapping and composite primary key - Logical column not found error

I have issues with mapping OneToMany foreign key to a composite primary key.我在将 OneToMany 外键映射到复合主键时遇到问题。 I have already tried many solutions, including this post @OneToMany and composite primary keys?我已经尝试了很多解决方案,包括这篇文章@OneToMany 和复合主键? . .

So the situation is:所以情况是:

I have two entities, say, Box & Color and the composite primary key is on the child side (Color).我有两个实体,例如 Box 和 Color,复合主键位于子端(Color)。

@Entity
@Table(name = "box")
data class Box(
        @Id
        var id: Int = 0,
        ...
       @OneToMany(cascade = [CascadeType.ALL])
        @JoinColumns(
                JoinColumn(name = "box_id", referencedColumnName = "id"),
                JoinColumn(name = "locale", referencedColumnName = "locale"))
        val colors: List<Color> = emptyList(),
)

@Entity
@Table(name = "color")
data class Color(
        
        @EmbeddedId
        var colorId: ColorId? = null,
) : Serializable

@Embeddable
data class ColorId(
        var id: Int = 0,
        @Column(name = "locale", insertable = false, updatable = false)
        var locale: Locale = Locale.Germany
) : Serializable

So, in the Box entity, I am trying to create an OneToMany mapping between Box & Color entities.因此,在 Box 实体中,我试图在 Box 和 Color 实体之间创建 OneToMany 映射。 For this should I use the composite primary key of the Color entity?为此,我应该使用 Color 实体的复合主键吗? If I try to join columns to the composite primary key(as I have done in the Box entity) I do get an error saying - unable to locate the logical column "locale".如果我尝试将列连接到复合主键(就像我在 Box 实体中所做的那样),我会收到一条错误消息 - 无法找到逻辑列“区域设置”。

How can I resolve this?我该如何解决这个问题?

Or what is the neat solution for this problem?或者这个问题的巧妙解决方案是什么?

OneToMany mapping will connect one Box with many Colors . OneToMany映射将一个Box与多个Colors连接起来。 Therefore, each Color needs to point to a Box , so each Color has a foreign key referencing the primary key of Box (of Int type).因此,每个Color都需要指向一个Box ,因此每个Color都有一个外键引用Box的主键Int类型)。

在此处输入图像描述

@OneToMany as an owning side @OneToMany 作为拥有方

In your mapping, you should use a single join column since this join column will be placed in the color table:在您的映射中,您应该使用单个连接列,因为此连接列将放置在color表中:

@Entity
@Table(name = "box")
data class Box(
        @Id
        var id: Int = 0,
        ...

        @OneToMany(cascade = [CascadeType.ALL])
        @JoinColumn(name = "box_id", referencedColumnName = "id")
        var colors: MutableList<Color> = mutableListOf()
)

You can check-out the working sample (Kotlin and Java) in my github repo .您可以在我的github repo中查看工作示例(Kotlin 和 Java)。

@OneToMany as an inverse (mapped-by) side @OneToMany 作为逆(映射)方

Database perspective数据库透视

@OneToMany as an owning side and @OneToMany as an inverse side (with a complimentary mandatory @ManyToOne ) produce the same database schema - a foreign key on the color table. @OneToMany作为拥有方@OneToMany作为反向方(带有免费的强制性@ManyToOne )产生相同的数据库模式 - color表上的外键。 That's why @JoinColumn annotation looks the same in both cases, no matter which side you place it on - the goal is to produce a foreign key on the many side.这就是为什么@JoinColumn注释在两种情况下看起来都一样,无论您将它放在哪一边——目标是在many产生一个外键。

Application perspective应用视角

The difference in 'owning-side' is in an application perspective. “拥有方”的区别在于应用程序的角度。 JPA/hibernate saves the relationships only from the owning side. JPA/hibernate 仅从拥有方保存关系。

So, if you change the owning side, you must set up this side (attribute) in your application code.因此,如果您更改拥有方,则必须在应用程序代码中设置此方(属性)。 In this case, you must set up Box in Color , otherwise, JPA/hibernate will not create a relationship (even though you added your Colors to Box ).在这种情况下,您必须在Color中设置Box ,否则 JPA/hibernate 不会创建关系(即使您将Colors添加到Box )。 Moreover, it will not raise any exception, just the relationship will not be created.而且,它不会引发任何异常,只是不会创建关系。 Next time, you will retrieve your Box from a database, the Color list will be empty.下次,您将从数据库中检索您的BoxColor列表将为空。

You can check-out the working samples and see the difference in ownership in my github repo .您可以在我的github repo中查看工作示例并查看所有权差异。

@Entity
@Table(name = "box")
data class Box(
        @Id
        var id: Int = 0,
        ...

        @OneToMany(cascade = [CascadeType.ALL], mappedBy="box")
        var colors: MutableList<Color> = mutableListOf(),
)

@Entity
@Table(name = "color")
data class Color(
        
        @EmbeddedId
        var colorId: ColorId? = null,
        
        // the owning side is changed, therefore you MUST set the box in Color
        // otherwise the relationship in a database will not be saved (!)
        @ManyToOne
        @JoinColumn(name = "box_id")
        var box: Box? = null
) : Serializable

Other possible problems其他可能的问题

Since you struggle with a OneToMany relationship, it might be useful for you to consider some other possible problems in this model.由于您遇到了OneToMany关系,因此考虑此 model 中的一些其他可能问题可能对您有用。

Locale non-insertable and non-updatable语言环境不可插入和不可更新

Another problem, you may encounter, is saving locale in ColorId as you marked it as non-insertable and non-updatable .您可能会遇到的另一个问题是将locale保存在ColorId中,因为您将其标记为non-insertablenon-updatable updatable 。 If this is on purpose that's fine (in that case all your colors must be pre-inserted into the database, or they will be inserted without Locale).如果这是故意的,那很好(在这种情况下,您的所有 colors 必须预先插入到数据库中,否则它们将在没有区域设置的情况下插入)。

Please, keep in mind, that setting Locale.GERMAN has no effect on a database in this case.请记住,在这种情况下,设置 Locale.GERMAN 对数据库没有影响。 It will be silently ignored and if you don't have such a color in a database, it will be inserted with null.它将被默默地忽略,如果您在数据库中没有这样的颜色,它将使用 null 插入。

Color assigned to only one box仅分配给一个盒子的颜色

If you model this relationship, you make a single Color (like Black in German) be assigned to only one Box.如果您使用 model 这种关系,您可以将单一颜色(如德语中的黑色)分配给一个Box。 It sounds a bit unnatural.听起来有点不自然。 Usually, I would assume Black can be assigned to many Boxes.通常,我会假设黑色可以分配给许多盒子。 So, it would be a ManyToMany relationship.所以,这将是一个ManyToMany关系。 Again, if this is on purpose, that's fine!同样,如果这是故意的,那很好!

ColorId as a primary key ColorId 作为主键

It's also a bit unnatural to have Locale as a part of a primary key in Color -- Black in German and Black in English as different colors?Locale作为Color主键的一部分也有点不自然——德语中的黑色和英语中的黑色作为不同的 colors? The colors themselves are locale-independent. colors 本身与语言环境无关。 The name of a color is locale-dependent, but it's more a matter of UI.颜色的名称取决于区域设置,但更多的是 UI 问题。 Again, if this is on purpose, that's fine, After all, it's your business model!同样,如果这是故意的,那很好,毕竟,这是您的商业模式!

Before providing you a solution, i would like to give you more info on the @OneToMany and the @JoinColumns annotation.在为您提供解决方案之前,我想为您提供有关@OneToMany@JoinColumns注释的更多信息。

A @OneToMany mapping symbolizes the fact that the Entity which has that mapping applied has many references to the other entity. @OneToMany映射表示应用了该映射的实体具有对另一个实体的许多引用。

ie A Box has many references to Color(s) in your case. ie A Box在您的情况下有很多对Color(s)的引用。

Now to model this, I would suggest you reverse the mapping and use @ManyToOne on the Color entity.现在到 model 这个,我建议你反转映射并在 Color 实体上使用@ManyToOne

@Entity
@Table(name = "color")
data class Color(
        
        @EmbeddedId
        var colorId: ColorId? = null,
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "box_id"), //referencedColumnName is not needed here as it is inferred as "id"
        var box: Box? = null
) : Serializable

Because the foreign key is supposed to be in Color.因为外键应该是颜色。 You cannot have foreign keys from Box to Color as there may be more than one Color.您不能有从 Box 到 Color 的外键,因为可能有多个 Color。

Also, note that it is not @JoinColumns , it is the singular @JoinColumn .另外,请注意它不是@JoinColumns ,而是单数@JoinColumn You need the plural version if the Box entity contains a Composite Primary Key .如果Box实体包含Composite Primary Key ,则需要复数版本。

With that being said, you can all together ignore the @OneToMany mapping, because if you need to get all Colors of a Box, you can and should in my opinion, simply use the Query API.话虽如此,您可以一起忽略@OneToMany映射,因为如果您需要获取 Box 的所有 Colors,那么在我看来,您可以而且应该使用Query API。

A @OneToMany mapping in this case would only be a convenience.在这种情况下, @OneToMany映射只是一种方便。 But if you insist on having the @OneToMany mapping then you can use the following.但是,如果您坚持使用@OneToMany映射,那么您可以使用以下内容。

@Entity
@Table(name = "box")
data class Box(
        @Id
        var id: Int = 0,
        ...
       @OneToMany(cascade = [CascadeType.ALL], mappedBy="box") // mappedBy will use the "box" reference from the Color Class
        val colors: List<Color> = emptyList(),
)

You have two options.你有两个选择。 Either define a join table @JoinTable(name = "color_box_assignment", joinColumns =...) or use an inverse mapping:定义连接表@JoinTable(name = "color_box_assignment", joinColumns =...)或使用逆映射:

@Entity
@Table(name = "color")
data class Color(
        
        @EmbeddedId
        var colorId: ColorId? = null,
        @ManyToOne(fetch = LAZY)
        @JoinColumns(
            JoinColumn(name = "box_id", referencedColumnName = "id"),
            JoinColumn(name = "locale", referencedColumnName = "locale"))
        var box: Box? = null
) : Serializable

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM