【问题标题】:Removing and persisting the same entity in a row in the same transaction in EJB using JPA使用 JPA 在 EJB 中的同一事务中删除和保留同一行中的同一实体
【发布时间】:2015-07-18 13:46:56
【问题描述】:

我有一个非常特殊的情况,我需要更新 JPA(EclipseLink 2.6.0)不允许的主键。因此,实体首先被删除,然后插入新值。

所涉及的表具有 GlassFish Server 需要用于 JAAS 身份验证的预定义结构。

mysql> describe user_role_table;
+-------------+---------------------+------+-----+---------+-------+
| Field       | Type                | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+-------+
| user_id     | varchar(176)        | NO   | PRI | NULL    |       |
| password    | varchar(255)        | NO   |     | NULL    |       |
| row_version | bigint(20) unsigned | NO   |     | 0       |       |
+-------------+---------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> describe group_table;
+---------------+---------------------+------+-----+---------+-------+
| Field         | Type                | Null | Key | Default | Extra |
+---------------+---------------------+------+-----+---------+-------+
| user_group_id | varchar(176)        | NO   | PRI | NULL    |       |
| group_id      | varchar(15)         | NO   | PRI | NULL    |       |
| row_version   | bigint(20) unsigned | NO   |     | 0       |       |
+---------------+---------------------+------+-----+---------+-------+
3 rows in set (0.01 sec)

user_group_idgroup_id 一起构成一个复合主键。 group_table 中的 group_id 是引用 user_role_table 中的 user_id 的外键。 GroupTable 拥有来自 @EmbeddableGroupTablePK@EmbeddedId

很少需要此信息。因此,我没有发布所涉及的实体类。


尝试通过以下方式模拟更新:首先删除提供的实体GroupTable,然后使用新值group_id 保持相同的实体,如下所示(在使用CMT 的EJB 中)。

同样,这是一个非常特殊的情况,甚至更新用户的权限也很少见。只是值得预先提供功能。

public GroupTable update(GroupTable groupTable, String userId, String oldGroupId) {
    String newGropuId = groupTable.getGroupTablePK().getGroupId();
    groupTable.getGroupTablePK().setGroupId(oldGropuId);

    if (delete(groupTable)) {
        // entityManager.flush();
        groupTable.setUserRoleTable(entityManager.getReference(UserRoleTable.class, userId));
        groupTable.getGroupTablePK().setGroupId(newGropuId);
        entityManager.persist(groupTable);
    }

    return groupTable;
}
public boolean delete(GroupTable groupTable) {
    groupTable.setUserRoleTable(entityManager.getReference(UserRoleTable.class, groupTable.getUserRoleTable().getUserId()));
    GroupTable managedGroupTable = entityManager.merge(groupTable);
    managedGroupTable.getUserRoleTable().getGroupTableList().remove(groupTable);
    entityManager.remove(managedGroupTable);
    return !entityManager.contains(managedGroupTable);
}

这些方法在同一个事务中执行,它们的工作做得很好,但前提是 update() 方法中唯一的注释行未注释。否则,它会抱怨 group_table 中主键的重复条目 - 首先要删除的实体在持久化该实体之前不会被删除,从而导致重复插入产生。

为什么在持久化实体之前需要entityManager.flush();?这是到数据库的额外往返,应该避免。

【问题讨论】:

  • 它是必需的,否则,persist 会取消您的删除调用,因此您只是更改主键。 JPA 中不允许更改主键,这就是您尝试使用删除/持久化来解决它的原因。克隆实体然后更新克隆的pk确实会更好。 EclipseLInk 具有克隆方法和策略来帮助实现这一点,并且使用克隆更准确地表示您正在做的事情 - 身份是实体的基础,因此新身份需要新实体。
  • 这有点不像看起来那么直观,因为删除调用被暂停,即使在删除后尝试保持分离的实例。看起来像this

标签: hibernate jpa ejb eclipselink


【解决方案1】:

Hibernate 文档说,

刷新是同步底层持久化的过程 存储在内存中的持久状态。

所以flush() 将与底层数据库同步您的持久状态(在您的情况下,它通过调用 delete(groupTable) 被删除groupTable)。简而言之,flushhibernate 会将这些更改写入 DB。

因此,当您评论entityManager.flush(); 时,hibernate 不会将更改与数据库同步(写入),导致它抱怨 group_table 中的主键条目重复。所以在这种情况下需要调用flush

注意:flush() 可能有助于在正在进行的事务和最后commit 更改之间保留数据。因此,如果之后出现问题,您也可以回滚之前的更改,例如批量插入/更新。

【讨论】:

  • 我接受我自己的答案,因为它实际上对我有用。谢谢你:)
【解决方案2】:

为了避免通过EntityManager#flush(); 进行额外的数据库往返,我使用 EclipseLink 特定的CopyGroup 克隆指定的实体,然后按照问题下方评论部分中 Chris 的建议将其持久化到数据库中。比如,

import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.sessions.CopyGroup;

public GroupTable update(GroupTable groupTable, UserTable userTable, String oldGroupId) {
    String newGroupId = groupTable.getGroupTablePK().getGroupId();
    groupTable.getGroupTablePK().setGroupId(oldGroupId);
    GroupTable copy = null;

    if (delete(groupTable)) {

        CopyGroup copyGroup = new CopyGroup();
        copyGroup.setShouldResetPrimaryKey(true);
        copyGroup.setShouldResetVersion(true);
        copyGroup.setDepth(CopyGroup.CASCADE_PRIVATE_PARTS); // Implicit in this case.

        copy = (GroupTable) entityManager.unwrap(JpaEntityManager.class).copy(groupTable, copyGroup);

        GroupTablePK groupTablePK = new GroupTablePK();
        groupTablePK.setGroupId(newGroupId);
        groupTablePK.setUserGroupId(groupTable.getGroupTablePK().getUserGroupId());

        copy.setGroupTablePK(groupTablePK);
        copy.getUserRoleTable().getGroupTableList().clear();
        UserRoleTable managedUserRoleTable = entityManager.find(UserRoleTable.class, userTable.getEmailId());
        copy.setUserRoleTable(managedUserRoleTable);
        managedUserRoleTable.getGroupTableList().add(copy); // Use a defensive link management method instead.
        entityManager.persist(copy);
    }

    return copy;
}

问题中显示的delete()方法保持不变。

CopyGroup.CASCADE_PRIVATE_PARTS 是默认深度级别,如果CopyGroup 没有明确指定属性,则表示只有私有的关系与实体中的所有其他属性一起被克隆.

如果CopyGroup 至少指定一个属性显式(使用CopyGroup#addAttribute()),则默认深度级别为CopyGroup.CASCADE_TREE,它仅复制@987654337 指定的属性@。

CopyGroup#setShouldResetPrimaryKey(true) - 设置是否为主 键应重置为空。 CopyGroup#setShouldResetVersion(true) - 设置版本 应该重置为null。


补充:

如果CopyGroup#setShouldResetVersion(true)(和true)与CASCADE_PRIVATE_PARTSCASCADE_ALL_PARTSNO_CASCADE中的任何一个一起使用,则不会设置克隆对象的主键属性。

如果CopyGroup#setShouldResetVersion(false)(与false)与CASCADE_TREE一起使用,则将复制/设置主键属性。否则,如果给定true(使用CASCADE_TREE),则不会设置CopyGroup#addAttribute()未指定的主键属性(除非明确指定,即需要明确)。

更多关于CopyGroup的详细信息可以在以下链接中找到。

【讨论】:

    猜你喜欢
    • 2015-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多