使用 join-class 级联问题休眠多对多
Hibernate Many-to-Many with join-class Cascading issue
我在 class Foo
和 Bar
之间有 Many-to-Many
关系。因为我想获得有关助手 table 的更多信息,所以我不得不制作一个助手 class FooBar
,如下所述:The best way to map a many-to-many association with extra columns when using JPA and Hibernate
我创建了一个 Foo,并创建了一些条形图(保存到 DB)。然后,当我使用
将其中一个栏添加到 foo 时
foo.addBar(bar); // adds it bidirectionally
barRepository.save(bar); // JpaRepository
然后创建了 FooBar 的数据库条目 - 正如预期的那样。
但是当我想再次从 foo 中删除同一个栏时,使用
foo.removeBar(bar); // removes it bidirectionally
barRepository.save(bar); // JpaRepository
那么较早创建的 FooBar 条目不会 从数据库中删除。
通过调试,我看到 foo.removeBar(bar);
确实双向删除了。没有抛出异常。
我是不是做错了什么?
我很确定它与级联选项有关,因为我只保存了栏。
我尝试过的:
在两个 @OneToMany - 注释上添加 orphanRemoval = true
,但没有用。我认为这是正确的,因为我没有删除 Foo 和 Bar,只是它们的关系。
从 @OneToMany 注释中排除 CascadeType.REMOVE,但与 orphanRemoval 相同,我认为这不适用于这种情况。
编辑: 我怀疑我的代码或模型中一定有什么东西与我的 orphanRemoval 混淆,因为现在已经有 2 个答案说它有效(orphanRemoval=true
).
最初的问题已得到解答,但如果有人知道什么可能导致我的 orphanRemoval 无法正常工作,我将非常感谢您的意见。谢谢
代码:Foo、Bar、FooBar
public class Foo {
private Collection<FooBar> fooBars = new HashSet<>();
// constructor omitted for brevity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER)
public Collection<FooBar> getFooBars() {
return fooBars;
}
public void setFooBars(Collection<FooBar> fooBars) {
this.fooBars = fooBars;
}
// use this to maintain bidirectional integrity
public void addBar(Bar bar) {
FooBar fooBar = new FooBar(bar, this);
fooBars.add(fooBar);
bar.getFooBars().add(fooBar);
}
// use this to maintain bidirectional integrity
public void removeBar(Bar bar){
// I do not want to disclose the code for findFooBarFor(). It works 100%, and is not reloading data from DB
FooBar fooBar = findFooBarFor(bar, this);
fooBars.remove(fooBar);
bar.getFooBars().remove(fooBar);
}
}
public class Bar {
private Collection<FooBar> fooBars = new HashSet<>();
// constructor omitted for brevity
@OneToMany(fetch = FetchType.EAGER, mappedBy = "bar", cascade = CascadeType.ALL)
public Collection<FooBar> getFooBars() {
return fooBars;
}
public void setFooBars(Collection<FooBar> fooBars) {
this.fooBars = fooBars;
}
}
public class FooBar {
private FooBarId id; // embeddable class with foo and bar (only ids)
private Foo foo;
private Bar bar;
// this is why I had to use this helper class (FooBar),
// else I could have made a direct @ManyToMany between Foo and Bar
private Double additionalInformation;
public FooBar(Foo foo, Bar bar){
this.foo = foo;
this.bar = bar;
this.additionalInformation = .... // not important
this.id = new FooBarId(foo.getId(), bar.getId());
}
@EmbeddedId
public FooBarId getId(){
return id;
}
public void setId(FooBarId id){
this.id = id;
}
@ManyToOne
@MapsId("foo")
@JoinColumn(name = "fooid", referencedColumnName = "id")
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
@ManyToOne
@MapsId("bar")
@JoinColumn(name = "barid", referencedColumnName = "id")
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
// getter, setter for additionalInformation omitted for brevity
}
Java Persistence 2.1. Chapter 3.2.3
Operation remove
• If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by X,
if the relationship from X to these other entities is annotated with
the cascade=REMOVE or cascade=ALL annotation element value.
• If X is
a managed entity, the remove operation causes it to become removed.
The remove operation is cascaded to entities referenced by X, if the
relationships from X to these other entities is annotated with the
cascade=REMOVE or cascade=ALL annotation element value.
检查您是否已经为实体Foo
(或FooBar
或Bar
)使用操作persist
。
我从示例代码中尝试了这一点。有几个 'sketchings in' 这重现了错误。
解决方案确实很简单,只需添加您提到的 orphanRemoval = true
。在 Foo.getFooBars()
上:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER, orphanRemoval = true)
public Collection<FooBar> getFooBars() {
return fooBars;
}
似乎最容易 post 复制到 GitHub - 希望还有更细微的差别或我错过的东西。
这是基于 Spring Boot 和 H2 内存数据库,因此不应该在其他环境下工作 - 如果有疑问,请尝试 mvn clean test
。
FooRepositoryTest
class有测试用例。它有一个删除链接 FooBar
的验证,或者它可能更容易阅读被记录的 SQL。
编辑
这是下面评论中提到的截图:
我已经测试了您的方案并进行了以下三处修改以使其正常工作:
- 将 orphanRemoval=true 添加到两个 @OneToMany getFooBars() 方法中Foo 和酒吧。对于您的特定场景,将它添加到 Foo 中就足够了,但是当您从 bar 中删除 foo 时,您可能想要同样的效果。
- 在用 Spring 的 @Transactional 注释的方法中包含 foo.removeBar(bar) 调用.你可以把这个方法放在一个新的@ServiceFooServiceclass中。
原因:orphanRemoval 需要一个活动的事务会话才能工作。
- 在调用 foo.removeBar(bar) 后删除了对 barRepository.save(bar) 的调用。
现在这是多余的,因为在事务会话中更改会自动保存。
我在 class Foo
和 Bar
之间有 Many-to-Many
关系。因为我想获得有关助手 table 的更多信息,所以我不得不制作一个助手 class FooBar
,如下所述:The best way to map a many-to-many association with extra columns when using JPA and Hibernate
我创建了一个 Foo,并创建了一些条形图(保存到 DB)。然后,当我使用
将其中一个栏添加到 foo 时foo.addBar(bar); // adds it bidirectionally
barRepository.save(bar); // JpaRepository
然后创建了 FooBar 的数据库条目 - 正如预期的那样。
但是当我想再次从 foo 中删除同一个栏时,使用
foo.removeBar(bar); // removes it bidirectionally
barRepository.save(bar); // JpaRepository
那么较早创建的 FooBar 条目不会 从数据库中删除。
通过调试,我看到 foo.removeBar(bar);
确实双向删除了。没有抛出异常。
我是不是做错了什么? 我很确定它与级联选项有关,因为我只保存了栏。
我尝试过的:
在两个 @OneToMany - 注释上添加
orphanRemoval = true
,但没有用。我认为这是正确的,因为我没有删除 Foo 和 Bar,只是它们的关系。从 @OneToMany 注释中排除 CascadeType.REMOVE,但与 orphanRemoval 相同,我认为这不适用于这种情况。
编辑: 我怀疑我的代码或模型中一定有什么东西与我的 orphanRemoval 混淆,因为现在已经有 2 个答案说它有效(orphanRemoval=true
).
最初的问题已得到解答,但如果有人知道什么可能导致我的 orphanRemoval 无法正常工作,我将非常感谢您的意见。谢谢
代码:Foo、Bar、FooBar
public class Foo {
private Collection<FooBar> fooBars = new HashSet<>();
// constructor omitted for brevity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER)
public Collection<FooBar> getFooBars() {
return fooBars;
}
public void setFooBars(Collection<FooBar> fooBars) {
this.fooBars = fooBars;
}
// use this to maintain bidirectional integrity
public void addBar(Bar bar) {
FooBar fooBar = new FooBar(bar, this);
fooBars.add(fooBar);
bar.getFooBars().add(fooBar);
}
// use this to maintain bidirectional integrity
public void removeBar(Bar bar){
// I do not want to disclose the code for findFooBarFor(). It works 100%, and is not reloading data from DB
FooBar fooBar = findFooBarFor(bar, this);
fooBars.remove(fooBar);
bar.getFooBars().remove(fooBar);
}
}
public class Bar {
private Collection<FooBar> fooBars = new HashSet<>();
// constructor omitted for brevity
@OneToMany(fetch = FetchType.EAGER, mappedBy = "bar", cascade = CascadeType.ALL)
public Collection<FooBar> getFooBars() {
return fooBars;
}
public void setFooBars(Collection<FooBar> fooBars) {
this.fooBars = fooBars;
}
}
public class FooBar {
private FooBarId id; // embeddable class with foo and bar (only ids)
private Foo foo;
private Bar bar;
// this is why I had to use this helper class (FooBar),
// else I could have made a direct @ManyToMany between Foo and Bar
private Double additionalInformation;
public FooBar(Foo foo, Bar bar){
this.foo = foo;
this.bar = bar;
this.additionalInformation = .... // not important
this.id = new FooBarId(foo.getId(), bar.getId());
}
@EmbeddedId
public FooBarId getId(){
return id;
}
public void setId(FooBarId id){
this.id = id;
}
@ManyToOne
@MapsId("foo")
@JoinColumn(name = "fooid", referencedColumnName = "id")
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
@ManyToOne
@MapsId("bar")
@JoinColumn(name = "barid", referencedColumnName = "id")
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
// getter, setter for additionalInformation omitted for brevity
}
Java Persistence 2.1. Chapter 3.2.3
Operation remove
• If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation element value.
• If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation element value.
检查您是否已经为实体Foo
(或FooBar
或Bar
)使用操作persist
。
我从示例代码中尝试了这一点。有几个 'sketchings in' 这重现了错误。
解决方案确实很简单,只需添加您提到的 orphanRemoval = true
。在 Foo.getFooBars()
上:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER, orphanRemoval = true)
public Collection<FooBar> getFooBars() {
return fooBars;
}
似乎最容易 post 复制到 GitHub - 希望还有更细微的差别或我错过的东西。
这是基于 Spring Boot 和 H2 内存数据库,因此不应该在其他环境下工作 - 如果有疑问,请尝试 mvn clean test
。
FooRepositoryTest
class有测试用例。它有一个删除链接 FooBar
的验证,或者它可能更容易阅读被记录的 SQL。
编辑
这是下面评论中提到的截图:
我已经测试了您的方案并进行了以下三处修改以使其正常工作:
- 将 orphanRemoval=true 添加到两个 @OneToMany getFooBars() 方法中Foo 和酒吧。对于您的特定场景,将它添加到 Foo 中就足够了,但是当您从 bar 中删除 foo 时,您可能想要同样的效果。
- 在用 Spring 的 @Transactional 注释的方法中包含 foo.removeBar(bar) 调用.你可以把这个方法放在一个新的@ServiceFooServiceclass中。
原因:orphanRemoval 需要一个活动的事务会话才能工作。 - 在调用 foo.removeBar(bar) 后删除了对 barRepository.save(bar) 的调用。
现在这是多余的,因为在事务会话中更改会自动保存。