如何级联更新到 Hibernate 中的子实体

How to cascade update to child entity in Hibernate

我有三个实体:Credentials、User 和 Admin。 User 和 Admin 实体都有一个字段 credentials,它使用 OneToOne 注释与 Credentials 实体相关。

通过 entityManager.merge 更新现有的用户或管理员条目时,我在 Credentials.login 列上获得了重复的键,该列具有唯一约束。

@Entity
@Table
public class Credentials {
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique=true, nullable=false, length=50)
    private String login;

    @Column(nullable=false, length=50)
    private String password;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

@Entity
@Table
public class User{
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /*********************************************
    *  Specific user columns here
    **********************************************/

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(nullable=false, name = "idCredentials")
    private Credentials credentials;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

@Entity
@Table
public class Admin{
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /*********************************************
    *  Specific admin columns here
    **********************************************/

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(nullable=false, name = "idCredentials")
    private Credentials credentials;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

我希望在调用 entityManager.merge(user) 后在各自的数据库表中更新用户和 user.credentials,但我收到错误消息“密钥的重复条目 'loginname' login_UNIQUE。Admin 实体也是如此。

提前感谢您的帮助。

您收到此错误的原因可能是 merge 将分离实体的内容复制到托管实体中。因此,如果您将包含 detachedUserAdmin 实体(从现在起称为 PERSON)作为参数传递给 merge =73=] Credentials 实体的副本保存在数据库中;那么你肯定会遇到这个问题。这样做的原因是:

  1. merge 会将 PERSON 实体参数的整个状态复制到相应的 context managed PERSON 实体中。
  2. 此副本将在 PERSON 参数中包含 Credentials 实体。
  3. 由于 Credentials 实体已分离,持久性管理器将假定此实体对应于尚未持久化的实体。
  4. 持久性上下文在刷新会话时,将使用 INSERT 保存 merged Credentials(坚持新的 Credentials)而不是 UPDATE.
  5. INSERT 将触发您正在获取的 login 字段上的重复约束违规,因为原始 Credentials 记录存在且 login 值正在使用INSERT.

编辑:(如何合并)

如果您不更新 PERSON 中的 Credentials,那么在 PERSON_DAO 中合并时,您可以:

  1. 在合并之前暂时从 PERSON (null) 中删除 Credentials
  2. 合并PERSON.
  3. 将原来的 Credentials 添加回您的 新合并的 PERSON

由于我无法访问您的 DAO 代码,这里是之前的伪代码:

    public PERSON mergeSafely(PERSON person) {
        Credentials originalCredentials = person.getCredentials();
        person.setCredentials(null);
        person = em.merge(person);
        person.setCredentials(originalCredentials);
        return person;
    }

如果您还想合并 Credentials,那么您应该(为了干净起见)使用 dao 层之上的 service 层。在该层中完成此操作的实用方法的伪代码如下所示:

    public PERSON mergeCompletely(PERSON person) {
        Credentials mergedCredentials = credentialsDAO.merge(person.getCredentials());
        person.setCredentials(mergedCredentials);
        person = personDAO.merge(person);
        return person;
    }

希望对您有所帮助。