JPA:一对一级联:哪一侧应该是层叠属性?

p8h8hvxi  于 2022-12-04  发布在  其他
关注(0)|答案(1)|浏览(92)

到目前为止,我的理解是,级联只有从父级到子级才有意义。现在我想知道:这是否也适用于一对一关系?
我这样问是因为我在我们的代码中发现了许多(单向的)OneToOne关系,它们从子节点级联到父节点。我用“persist”测试了它,它似乎起作用了--这意味着暂时的子节点和暂时的父节点一起被持久化。在查阅文献时,我发现了这样一个级联策略的例子。例如在baeldung上:

// 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's blog上:

//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;

}

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

nwsw7zdq

nwsw7zdq1#

首先,我不是一个CASCADE的粉丝。为什么?仅仅是因为如果你不知道发生了什么,为什么要使用一个东西?
我不能回答你的问题,因为这取决于你认为什么是正确的。但我可以帮助阐明一些问题。
您需要了解“拥有实体”。从Javadoc:
如果关系是双向的,则非拥有方必须使用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=?

或者,如果现有地址记录存在,您可以先将其删除,但之后必须先进行检查,依此类推。这是一个不错的解决方案。请确保在事务中执行此操作。
或者你可以完全避免使用cascade,你必须自己保存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
或者,就把它放在一边,开心点。

相关问题