【问题标题】:JPA: @OneToMany mapping and composite primary key - Logical column not found errorJPA:@OneToMany 映射和复合主键 - 找不到逻辑列错误
【发布时间】:2021-04-12 11:46:48
【问题描述】:

我在将 OneToMany 外键映射到复合主键时遇到问题。我已经尝试了很多解决方案,包括这个帖子@OneToMany and composite primary keys?

所以情况是:

我有两个实体,例如 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

因此,在 Box 实体中,我尝试在 Box 和 Color 实体之间创建 OneToMany 映射。为此,我应该使用 Color 实体的复合主键吗?如果我尝试将列连接到复合主键(就像我在 Box 实体中所做的那样),我会收到一条错误消息 - 无法找到逻辑列“区域设置”。

我该如何解决这个问题?

或者这个问题的巧妙解决方案是什么?

【问题讨论】:

    标签: java hibernate kotlin jpa hibernate-mapping


    【解决方案1】:

    OneToMany 映射将连接一个Box 和多个Colors。因此,每个Color需要指向一个Box,所以每个Color都有一个外键引用Box主键Int类型)。

    @OneToMany 作为拥有方

    在您的映射中,您应该使用单个连接列,因为此连接列将放置在 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()
    )
    

    您可以在我的github repo 中查看工作示例(Kotlin 和 Java)。

    @OneToMany 作为逆(映射)边

    数据库视角

    @OneToMany 作为拥有方@OneToMany 作为反方(带有免费的强制@ManyToOne)生成相同的数据库架构 - 外键在color 表上。这就是为什么 @JoinColumn 注释在两种情况下看起来都是一样的,无论你把它放在哪一边——目标是在 many 一边产生一个外键。

    应用视角

    “拥有方”的区别在于应用程序的角度。 JPA/hibernate 仅从拥有方保存关系。

    因此,如果您更改拥有方,则必须在应用程序代码中设置此方(属性)。在这种情况下,您必须在Color 中设置Box,否则JPA/hibernate 将不会创建关系(即使您将Colors 添加到Box)。而且,它不会引发任何异常,只是不会创建关系。下次,您将从数据库中检索您的BoxColor 列表将为空。

    您可以在我的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
    
    

    其他可能的问题

    由于您在处理 OneToMany 关系时遇到了困难,因此考虑此模型中的其他一些可能问题可能对您有用。

    语言环境不可插入和不可更新

    您可能遇到的另一个问题是将locale 保存在ColorId 中,因为您将其标记为non-insertablenon-updatable。如果这是故意的,那很好(在这种情况下,您的所有颜色都必须预先插入到数据库中,否则它们将在没有区域设置的情况下插入)。

    请记住,在这种情况下,设置 Locale.GERMAN 对数据库没有影响。它将被默默地忽略,如果您在数据库中没有这样的颜色,它将被插入为 null。

    颜色只分配给一个盒子

    如果您对这种关系进行建模,您可以将一种颜色(如德语中的黑色)分配给一个框。听起来有点不自然。通常,我会假设黑色可以分配给许多盒子。所以,这将是一个ManyToMany 关系。同样,如果这是故意的,那很好!

    ColorId 作为主键

    Color 中将Locale 作为主键的一部分也有点不自然——德语中的黑色和英语中的黑色是不同的颜色?颜色本身与语言环境无关。颜色的名称取决于区域设置,但更多的是 UI 问题。同样,如果这是故意的,那很好!毕竟,这是你的商业模式!

    【讨论】:

    • 但是 Color 有复合主键(id & locale)。在这种情况下不应该考虑语言环境吗?
    • 不,您在颜色一侧有很多,在盒子一侧有一个。外键在多方面并引用一方面。因此,外键将放在 Color 中,并引用 Box id,这是一个简单的 int。
    • @ThirumalaiParthasarathi 我的代码很好并且经过测试。 :) 查看 github 存储库(目前使用 Java,但映射完全相同):github.com/pwegrzynowicz/stackoverflow-examples/tree/master/…OneToMany 中的 ManyToOnemappedBy 方法也可以使用。但它会改变关系的拥有方,因此这取决于作者喜欢什么以及他的用例是什么。
    • @ThirumalaiParthasarathi 并将 Kotlin 示例添加到 repo。你可以清楚地看到它正在工作。此外,您还可以了解更改所有权对代码的影响。
    • 你是对的。我一定是看错了你的帖子。但是有一点,您在这里使用的策略是单向的,性能不高。我仍然更喜欢 Color 实体上的@ManyToOne(可能是带有@OneToMany 的双向映射,带有mappedBy 属性以指定另一方拥有连接)。它也比单向连接更高效。
    【解决方案2】:

    你有两个选择。要么定义连接表@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
    

    【讨论】:

    • 通过逆向映射,复合键会被处理吗? Patrycja 在下面的帖子中提出的解决方案怎么样?这也有效,但我不确定那里是否考虑了复合键?
    • 是的,它将处理复合键。 Partycja 给出的答案是 IMO 不正确,但与我的方向相同。
    • 我确实收到以下错误:org.hibernate.MappingException: Unable to find column with logical name: locale in box
    • 您现在使用什么映射?一步一步地接近这一点。首先尝试将box 关联添加到Color。这应该只是工作。然后在另一边加上逆@OneToMany(mappedBy = "box") Set&lt;Color&gt; colors
    • @ChristianBeikov 我的解决方案很好并且可以工作,请查看示例代码(目前使用 Java,但映射完全相同):github.com/pwegrzynowicz/stackoverflow-examples/tree/master/…ManyToOne 的方法也可以,但它改变了关系的owning 方面。因此,这取决于作者的用例。
    【解决方案3】:

    在为您提供解决方案之前,我想向您提供有关@OneToMany@JoinColumns 注释的更多信息。

    @OneToManymapping 表示应用了该映射的实体对另一个实体有许多引用。

    即在您的情况下,Box 有很多对 Color(s) 的引用。

    现在要对此进行建模,我建议您反转映射并在 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
    

    因为外键应该是彩色的。您不能有从 Box 到 Color 的外键,因为可能有多个 Color。

    另外,请注意它不是 @JoinColumns,它是单数@JoinColumn。如果Box 实体包含Composite Primary Key,则需要复数形式。

    话虽如此,你们可以一起忽略@OneToMany 映射,因为如果您需要获取一个盒子的所有颜色,我认为您可以而且应该使用Query API。

    在这种情况下,@OneToMany 映射只是为了方便。但是,如果您坚持使用 @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(),
    )
    

    【讨论】:

    • 在 @ManyToOne(fetch = LAZY) 只有 box_id 加入完成..,@JoinColumn(name = "box_id") :其他属性 locale 怎么样。需要加入语言环境吗?
    • @SGuru 在您的问题中,locale 字段位于 Color 实体中。为什么您认为需要 locale 来引用 Box 实体?
    猜你喜欢
    • 1970-01-01
    • 2019-04-22
    • 2017-01-30
    • 2020-10-04
    • 2018-05-08
    • 1970-01-01
    • 2021-10-07
    • 2020-04-15
    • 2013-12-24
    相关资源
    最近更新 更多