JPA: Cascading OneToOne:哪一侧应该是级联属性?



到目前为止,我的理解是级联只从父级到子级有意义。现在我想知道:这是否也适用于OneToOne关系?

我问这个问题是因为我发现在我们的代码中有许多(单向)OneToOne关系,从子级到父级。我用"persist"进行了测试。这似乎是有效的——这意味着暂时的孩子和暂时的父母在一起。在查阅文献时,我发现了这种级联策略的例子。例如:

// CHILD    
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//... 
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
}

//PARENT
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(mappedBy = "address")
private User user;
}

在其他一些文章中,层叠是以另一种方式完成的。例如在Vlad Mihalcea的博客上:

//CHILD
@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {
@Id
@GeneratedValue
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
}
//PARENT
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
@OneToOne(mappedBy = "post", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PostDetails details;
}

那么最后它们都是两个有效的选项吗?我很困惑。

首先,我不是CASCADE的粉丝。为什么?很简单,如果你不知道发生了什么,为什么要用一个东西?

我不能回答你的问题,因为这取决于你认为什么是正确的。但我可以帮你弄清楚一些问题。

您需要了解"所属实体"。来自Javadocs:

如果关系是双向的,非拥有方必须使用OneToOne注释的mappedBy元素来指定拥有方的关系字段或属性。

在第一种情况下,User是所有者实体。在JPA中,只有拥有方会保持关系。在这种情况下,只有在User实体中设置了Address字段,JPA才会尝试持久化关系。

持久化操作将是:

public void initWithOwner() {
Address a = Address.builder().build();
userRepo.save(User.builder().address(a).build());
}

结果是:

Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into address (id) values (?)
Hibernate: insert into users (address_id, id) values (?, ?)

没问题。但是如果Address已经被持久化了呢?

public void initWithOwnerTransient() {
try {
Address a = addressRepo.save(Address.builder().build());
userRepo.save(User.builder().address(a).build());
} catch (Exception e) {
System.out.println("SAVE ERROR: " + e.getMessage());
}
}

结果:

Hibernate: call next value for hibernate_sequence
Hibernate: insert into address (id) values (?)
Hibernate: call next value for hibernate_sequence
SAVE ERROR: detached entity passed to persist: com.example.jpaplay.Address; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.jpaplay.Address

使用Cascade有问题,因为它试图插入两个,但当前地址已经存在。你可以绕过它:

private void initWithOwnerTransientFix() {
try {
User u = userRepo.save(User.builder().build());
Address a = addressRepo.save(Address.builder().build());
u.setAddress(a);
u = userRepo.save(u);
} catch (Exception e) {
System.out.println("SAVE ERROR: " + e.getMessage());
}
}

这就产生了这段可爱的SQL:

Hibernate: call next value for hibernate_sequence
Hibernate: insert into users (address_id, id) values (?, ?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into address (id) values (?)
Hibernate: select user0_.id as id1_1_1_, user0_.address_id as address_2_1_1_, address1_.id as id1_0_0_ from users user0_ left outer join address address1_ on user0_.address_id=address1_.id where user0_.id=?
Hibernate: select address0_.id as id1_0_0_ from address address0_ where address0_.id=?
Hibernate: select user0_.id as id1_1_1_, user0_.address_id as address_2_1_1_, address1_.id as id1_0_0_ from users user0_ left outer join address address1_ on user0_.address_id=address1_.id where user0_.address_id=?
Hibernate: update users set address_id=? where id=?

或者您可以先删除现有的地址记录,如果它存在的话,但是您必须先检查,依此类推。这是个不错的解决方案。请确保在事务中这样做。

或者你可以完全避免使用级联。您必须自己保存AddressUser,或者使用已经保存的实例,这也意味着您必须先检查,但至少您"知道"。你在干什么。

private void initWithOwnerNoCascade() {
try {
AddressNoCascade a = addressNoCascadeRepo.save(AddressNoCascade.builder().build());
userNoCascadeRepo.save(UserNoCascade.builder().address(a).build());
} catch (Exception e) {
System.out.println("SAVE ERROR: " + e.getMessage());
}
}

结果:

Hibernate: call next value for hibernate_sequence
Hibernate: insert into address (id) values (?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into usersnocascade (address_id, id) values (?, ?)

那么,你应该把Cascade放在哪一边?如果你要坚持使用它,让你的生活变得更复杂,它应该在关系的拥有方,在这种情况下是User

或者,不去管它,开心点。

最新更新