【问题标题】:Bi-directional @OneToOne with ONLY CascadeType.REMOVE saves changes: that is not wanted只有 CascadeType.REMOVE 的双向 @OneToOne 保存更改:这是不想要的
【发布时间】:2014-11-27 11:34:53
【问题描述】:

问题:

我有两个实体,SomeEntitySomeEntityInfo,它们是双向一对一关系,只有 CascadeType.REMOVE 级联集。

如果 SomeEntity.someEntityInfo 已更改,并且 SomeEntity 已(已存在)保存 -> 不应发生对 SomeEntityInfo 表/对象的级联数据库更新。

但是,相关实体也会更新

编辑/更新 换句话说:我希望SomeEntityInfo 是“(有点)不可变的”。它应该在创建 SomeEntity 时创建,但不更新/检查版本 - 乐观锁定 - 如果重新保存 SomeEntity

到目前为止我做了什么

  • SomeEntity 的getter 中返回SomeEntityInfo 的副本会导致

    通过未标记为 cascade PERSIST [..] 的关系找到了一个新对象

  • (拼命地)注释

    @OneToOne(cascade = { CascadeType.REMOVE })
    @JoinColumn(name = "someentityinfo_id", updatable = false, insertable = true)
    private SomeEntityInfo someEntityInfo;
    

    与外键的ID有关,与被引用对象内部的数据无关

示例 - 数据库架构 (mysql db)

    CREATE TABLE someentity (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      version INT(11) NULL DEFAULT NULL,
      someentityinfo_id INT(11) UNSIGNED NULL DEFAULT NULL,
      PRIMARY KEY (id)
    )
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB;

    CREATE TABLE someentityinfo (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      version INT(11) NULL DEFAULT NULL,
      status varchar(255) DEFAULT NULL,
      PRIMARY KEY (id)
    )
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB;

    ALTER TABLE someentity
      ADD INDEX FK_someentityinfo_id (someentityinfo_id);

    ALTER TABLE someentity
      ADD CONSTRAINT FK_someentityinfo_id FOREIGN KEY (someentityinfo_id) REFERENCES someentityinfo (id);

实体代码

一些实体

    import javax.persistence.CascadeType;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.OneToOne;
    import javax.persistence.Table;
    import javax.persistence.Version;

    @Entity
    @Table(name = "someentity")
    public class SomeEntity {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;

        @Version
        private Integer version;

        @OneToOne(cascade = { CascadeType.REMOVE })
        @JoinColumn(name = "someentityinfo_id")
        private SomeEntityInfo someEntityInfo;
        [getter/setter]
    }

一些实体信息

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.OneToOne;
    import javax.persistence.Table;
    import javax.persistence.Version;

    @Entity
    @Table(name = "someentityinfo")
    public class SomeEntityInfo {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;

        @Version
        private Integer version;

        private String status;

        @OneToOne(mappedBy = "someEntityInfo")
        private SomeEntity someEntity;
        [getter/setter]
    }

使用的测试场景

    // create and persist entity and its info object
    em.getTransaction().begin();
    SomeEntity se = new SomeEntity();
    SomeEntityInfo seInfo = new SomeEntityInfo();
    se.setSomeEntityInfo(seInfo);
    seInfo.setSomeEntity(se);

    seInfo.setStatus("status 1");

    em.persist(se);
    em.persist(se.getSomeEntityInfo());
    em.getTransaction().commit();

    // load created entity, modify its info and expect
    // to NOT update the info object while saving the entity again
    em.getTransaction().begin();
    SomeEntity loadedSe = em.find(SomeEntity.class, Integer.valueOf(se.getId()));

    loadedSe.getSomeEntityInfo().setStatus("do not cascade update");

    // as Chris said below, not necessary to explicit save managed entity again
    // em.persist(loadedSe);

    em.getTransaction().commit();

环境

EclipseLink,版本:Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd

其他信息

规范 (http://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_(ELUG)#.40OneToOne) sais:

cascade – 默认情况下,JPA 不会将任何持久性操作级联到关联的目标。

事实并非如此(更改是级联的)..我在这里缺少什么?

【问题讨论】:

    标签: java jpa orm eclipselink


    【解决方案1】:

    SomeEntityInfo 实例是托管,这意味着对它的任何更改都将被保存,与级联无关。更多详情请查看this answer

    【讨论】:

    • 好的,所以.. 有没有办法在这种情况下“取消管理”引用的实体? (我尝试 getter 提供实体的副本 - “不可变”,效果是“通过未标记为级联 PERSIST 的关系找到了一个新对象”
    【解决方案2】:

    更改实际上并没有级联。 CascadeType.REMOVE 的意思本质上是 ON DELETE CASCADE,即删除任何孤立的行。虽然有一个ON UPDATE CASCADE,但它使用较少,只影响外键的另一端。

    如果您对由 ORM 处理的对象进行更改,则更改将被保留。但是它与级联无关。

    因此,如果您不想更新数据库中的SomeEntityInfo,请不要在您的代码中更新它。 EclipseLink 在这里做得非常好。

    【讨论】:

    • 那么,我做对了吗:除了“不要更改相关对象”之外,没有办法阻止对象图上的更新/值更改?
    • @arkanoid256 你告诉 ORM “嘿,你处理这些对象。如果它们发生变化,请将更改写入数据库”。这就是正在发生的事情,所以没有什么令人惊讶的。你为什么还要修改 SomeEntityInfo?
    • 嗨,对不起,迟到的回应:我不想级联对“SomeEntity”引用的“SomeEntityInfo”对象的任何更新(从长远来看,没有版本更改/乐观锁定 - “SomeEntityInfo " 可以在程序生命周期中更改,但不应该在坚持“SomeEntity”时保存 -> 只有在删除“SomeEntity”时才会保存。.. 使“SomeEntityInfo”过时.. 也将其删除)
    • 那是不可能的。如果您更改SomeEntityInfo,它将被保留。可能有一些技巧可以防止它(比如将其从会话中分离或其他),但重点仍然是:如果您不想持久更改,请不要进行更改。进行更改的是您的代码,所以这就是您的问题所在。
    【解决方案3】:

    下面的答案是正确的,我只是想补充一点,em.persist() 没有做任何事情——它是一个无操作,因为加载的Se 实例是一个托管实体。如果它不是托管实体,则 JPA 规范要求在现有的非托管实体实例上调用 persist 会导致异常——无论是在 persist 或 flush 调用上,还是在事务尝试提交时。

    在调用刷新或事务提交时,对托管实体的所有更改都会同步到数据库。从 EntityManager 访问的所有实体都由该 EntityManager 上下文管理,直到它们被清除。如果您在进行更改之前调用了 em.clear,则提交不会获取更改。另一种选择是在查找操作上使用只读查询提示:http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Query_Hints#Read_Only

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多