【问题标题】:JPA - Removing of elements of a bidirectional RelationshipJPA - 删除双向关系的元素
【发布时间】:2011-03-29 02:28:17
【问题描述】:

为什么我可以删除双向关系的元素,尽管关系的一侧是在持久性上下文中管理的(示例 I)?当我有一个不起作用的单向关系时(参见示例 II)。为什么?

实体:

@Entity
Class User {
    ...
    @OneToMany(mappedBy = "user")
    private List<Process> processes;

    @OneToOne // Unidirectional
    private C c;
    ...

    @PreRemove
    private void preRemove() {
        for (Process p : processes) {
            p.internalSetUser(null);
        }
    }
   ...
}

@Entity
Class Process {
    ...
    @ManyToOne
    private User user;
    ...

    @PreRemove
    protected void preRemove() {
        if (this.user != null) {
            user.internalRemoveProcess(this);
        }
    }
   ...
}

@Entity
Class C {

 }

示例一:

// Create User u1 with Processes p1, p2

tx.start();
// Only u1 is manged in persistence context and no process
userFacade.delete(u1); // There following is called: >> em.remove(em.merge(u1)); // Works
tx.commit();

示例二:

// Create User u and Object C c, establish their relation.

tx.start();
cFacade.remove(c); //>>MySQLIntegrityConstraintViolationException,foreign key constraint fails
ty.commit();

在第一个示例中,我使用这些内部方法在每种情况下设置关系的另一端,但我认为该另一端不是在持久性上下文中管理的?!当我更改用户的进程并保存用户时,该进程不会更新,除非我使用 cascade.MERGE 或者两者都加载到事务中并因此在 pc 中进行管理。那么为什么移除会起作用呢?

【问题讨论】:

    标签: orm jpa relationship jpa-2.0 bidirectional


    【解决方案1】:

    在示例 II 中,我猜您必须先调用 user.setC(null),然后才能删除 c。

    在示例 I 中,这是我的理解。您首先合并u1,因此u1' 被加载到PC 中,u1 的状态被复制到u1'(这就是因为您没有级联MERGE),然后返回。然后调用 remove(在 u1' 上),preRemove 被调用并更改 p1' 和 p2'。它们因此是脏的,并且会在刷新时得到适当的更新(将 FK 设置为NULL),而u1' 将被删除。一切正常。

    以防万一,以下是 JPA 2.0 规范中合并操作的语义:

    3.2.7.1 合并分离实体状态

    合并操作允许 从分离状态传播 实体到持久实体 由实体经理管理。

    合并操作的语义 应用于实体 X 如下:

    • 如果X 是一个分离的实体,X 的状态被复制到一个 预先存在的托管实体实例 X' 相同的身份或新的 已创建 X 的托管副本 X'。
    • 如果X是一个新的实体实例,一个新的托管实体实例X'是 创建并复制X 的状态 进入新的托管实体实例 X'。
    • 如果X 是已删除的实体实例,则IllegalArgumentException 将是 由合并操作(或 事务提交将失败)。
    • 如果X是一个托管实体,它会被合并操作忽略, 但是,合并操作是 级联到引用的实体 来自X 的关系,如果这些 关系已用 级联元素值 cascade=MERGE 或 cascade=ALL 注释。
    • 对于由来自X 的关系引用的所有实体Y,具有 级联元素值cascade=MERGE 或cascade=ALL、Y 合并 递归为Y'。对于所有这样的Y 由X 引用,X' 设置为 参考Y'。 (请注意,如果 X 是 托管然后X 是同一个对象 X'.)
    • 如果X 是合并到X' 的实体,则引用另一个实体 Y,其中cascade=MERGE 或 cascade=ALL 未指定,则 同一个协会的导航 来自X' 产生对 a 的引用 托管对象Y' 同 永久标识为Y。

    持久性提供者不得 合并标记为 LAZY 的字段 已获取:它必须忽略此类 合并时的字段。

    任何Version 使用的列 实体必须由 持久性运行时实现 在合并操作期间和/或在 刷新或提交时间。离席期间 Version 列中没有 额外的版本检查由 持久性提供程序运行时 在合并操作期间。

    参考

    • JPA 2.0 规范
      • 3.2.7.1 合并分离实体状态

    【讨论】:

    • 谢谢帕斯卡!我认为在这两种情况下你都是对的。例如我是上述规范的最后一点。
    • 如果我使用示例我不合并但以下内容:"User u1Managed = em.find(u1.getId()); em.remove(u1Managed);" 然后它也可以工作。这意味着查找操作也会产生到托管 p1' 和 p2'?
    • @Michael processes 是懒惰的,但我认为 p1 和 p2 会加载到您的 PreRemove 中(并因此成为真正的管理)。
    【解决方案2】:

    这是预期行为:

    由于关系是双向的,因此应用程序更新 关系的一方,另一方也应该得到更新, 并保持同步。在 JPA 中,就像在 Java 中一般 应用程序或维护对象模型的责任 关系。 如果您的应用程序添加到 关系,那么它必须添加到另一边。

    这可以通过对象模型中的add或set方法解决 处理关系的双方,所以应用程序代码 不需要担心。有两种方法可以解决这个问题, 您可以只将关系维护代码添加到一侧 的关系,并且只使用来自一侧的设置器(例如 使另一侧受到保护),或将其添加到两侧并确保 避免无限循环。

    例如:

    public class Employee {
        private List phones;
        ...
        public void addPhone(Phone phone) {
            this.phones.add(phone);
            if (phone.getOwner() != this) {
                phone.setOwner(this);
            }
        }
        ...
    }
    

    来源:OneToMany#Getters_and_Setters

    【讨论】:

      猜你喜欢
      • 2017-06-23
      • 1970-01-01
      • 2011-12-06
      • 1970-01-01
      • 1970-01-01
      • 2015-08-24
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      相关资源
      最近更新 更多