删除元素时使用 JoinTable 和 OrderColumn 的 Hibernate 单向 OneToMany 映射中的约束冲突

Posted

技术标签:

【中文标题】删除元素时使用 JoinTable 和 OrderColumn 的 Hibernate 单向 OneToMany 映射中的约束冲突【英文标题】:Constraint violation in Hibernate unidirectional OneToMany mapping with JoinTable and OrderColumn when removing elements 【发布时间】:2011-04-30 16:00:04 【问题描述】:

从如上所述映射的列表中删除元素时遇到问题。这是映射:

@实体 @表(名称=“富”) 类Foo 私人列表酒吧; @OneToMany @OrderColumn( 名称 = "order_index" ) @JoinTable(name = "foo_bar_map", joinColumns = @JoinColumn(name = "foo_id"), inverseJoinColumns = @JoinColumn(name = "bar_id")) @Fetch(FetchMode.SUBSELECT) 公共列表 getBars() 返回栏;

插入 Bar 实例并保存 Foo 工作正常,但是当我从列表中删除一个元素并再次保存时,违反了映射表中 bar_id 的唯一约束。以下 SQL 语句由 hibernate 发出,它们看起来很奇怪:

日志:执行:从 foo_bar_map 中删除,其中 foo_id=$1 和 order_index=$2 详细信息:参数:$1 = '4',$2 = '6' 日志:执行 S_5:更新 foo_bar_map 设置 bar_id=$1 其中 foo_id=$2 和 order_index=$3 详细信息:参数:$1 = '88',$2 = '4',$3 = '0' 错误:重复键值违反唯一约束“foo_bar_map_bar_id_key”

鉴于 Hibernate 生成的语句(列表中有五个项目,我删除第一个项目,Hibernate 删除带有 LAST 索引的映射行并尝试更新其余的,从第一个)。

上面的映射有什么问题?

【问题讨论】:

【参考方案1】:

通常当通过连接表连接时,关系是多对多而不是单对多。试试这个

@ManyToMany
@OrderColumn( name = "order_index" )
@JoinTable( name = "foo_bar_map", joinColumns = @JoinColumn( name = "foo_id" ), inverseJoinColumns =  @JoinColumn( name = "bar_id" ) )
@Fetch( FetchMode.SUBSELECT )
public List getBars() 
    return bars;

【讨论】:

但从我的领域模型的角度来看,它只是 OneToMany。用 ManyToMany 替换它会利用一些约束,这显然不是我想要的。我希望架构尽可能安全。这里使用 JoinTable 的主要原因是为了跟踪订单索引,这在 Bar 类中不应该知道。 使用标准 JPA,单向 OneToMany 使用连接表进行映射(尽管 Hibernate 允许使用 JoinColumn 在没有连接表的情况下进行映射,现在 JPA 2.0 也支持此功能)。跨度> 【参考方案2】:

我猜你需要的是正确的逆映射。http://docs.jboss.org/hibernate/core/3.3/reference/en/html/tutorial.html#tutorial-associations-bidirectional

【讨论】:

但我希望这种关联是单向的。 Bar 类不应该知道 Foo 引用它的任何事情。【参考方案3】:

您的映射完全有效,并且可以与 EclipseLink 作为 JPA 2.0 实现一起使用(当然没有 Fetch 注释),但在 Hibernate 中确实失败了。

这是带有 Hibernate 的 DDL:

create table foo_bar_map (foo_id bigint not null, bar_id bigint not null, order_index integer not null, primary key (foo_id, order_index), unique (bar_id))
alter table foo_bar_map add constraint FK14F1CB7FA042E82 foreign key (bar_id) references Bar4022509
alter table foo_bar_map add constraint FK14F1CB7B6DBCCDC foreign key (foo_id) references Foo4022509

假设Foo#1 拥有一个包含Bar#1Bar#2Bar#3 的列表,连接表包含:

foo_id | bar_id | order_index
     1 |      1 |           1
     1 |      2 |           2
     1 |      3 |           3

当删除时,比如说列表中的第一项,Hibernate 首先delete 连接表中的最后一行(WTF?):

foo_id | bar_id | order_index
     1 |      1 |           1
     1 |      2 |           2

然后尝试update 连接表中的bar_id 列而不是order_index(WTF!?)以反映列表中项目的“新”顺序。首先(示意图):

foo_id | bar_id | order_index
     1 |      2 |           1
     1 |      2 |           2

下一步将导致:

foo_id | bar_id | order_index
     1 |      2 |           1
     1 |      3 |           2

显然,由于bar_id 上的unique 约束,这种方法听起来不正确并且不起作用。更一般地说,为什么 Hibernate 会搞乱bar_id 而不是更新order_index 列?

我认为这是一个 Hibernate 错误(报告为 HHH-5694,现在请参阅 HHH-1268)。

【讨论】:

谢谢,是的,这也是我在调试器中花费了一段时间后发现的。作为修复,我现在正在使用 ManyToMany(它放弃了唯一性约束),但感觉很难看。有趣的是,没有其他人遇到过这个问题,因为我认为这是一个常见的用例...... @tbh 是的,使关联成为多对多将删除唯一约束(以允许“多”)。但这确实是一种解决方法,Hibernate 应该正确处理一对多。您可能想为 Jira 问题投票 :)【参考方案4】:

不,我不认为这是一个休眠错误,如果您进行搜索,您会看到 Pascal Thivent 引用的这个休眠错误是自 2006 年以来已知的一个错误,并且从未得到解决。

为什么?

因为我认为问题只是在表上的约束而不是在休眠中。

我不明白为什么 bar_id 有唯一约束

使用订单索引意味着您的集合是一个列表(而不是一个集合!)。 List 是一个集合,您可以在其中为您添加的元素指定索引(它对应于 OrderColumn)。 List 和 Set 之间的区别在于您可以两次(或更多)相同的数据,但相同的数据将位于不同的索引处。 然后你可以有相同的 bar_id 不同的索引,你不必在 bar_id 上指定唯一的约束。 并且主键不能是 (foo_id, order_index) 导致模式 List 在不同的索引处授权相同的数据。 也许你的 PK 应该是 (foo_id, bar_id, order_index) ?

我认为问题出在这种方式:)

【讨论】:

听说过有序集吗?这两个约束是正交的:定义的顺序和唯一性。 true 可以通过SortedSet完成docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/…

以上是关于删除元素时使用 JoinTable 和 OrderColumn 的 Hibernate 单向 OneToMany 映射中的约束冲突的主要内容,如果未能解决你的问题,请参考以下文章

sql中order by和group by的区别

JoinTable 注释导致 NullPointerException

order set结构及命令详解

在哪种情况下使用 JPA @JoinTable 注释?

Redis 之order set有序集合结构及命令详解

IntegrityError:删除时违反外键