使用 JPA 在 EJB 中的同一事务中连续删除和保留同一实体
Removing and persisting the same entity in a row in the same transaction in EJB using JPA
我有一个非常特殊的情况,我需要更新 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_id
和group_id
一起构成复合主键。 group_table
中的 group_id
是引用 user_role_table
中的 user_id
的外键。 GroupTable
持有来自 @Embeddable
class、GroupTablePK
.
的 @EmbeddedId
很少需要此信息。因此,我不会发布涉及的实体 class。
尝试通过首先删除提供的实体 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();
?这是到数据库的额外往返,应该避免。
Hibernate 文档说,
Flushing is the process of synchronizing the underlying persistent
store with persistable state held in memory.
因此 flush()
会将您的持久状态(在您的情况下通过调用 delete(groupTable) 删除 groupTable
)与底层数据库同步。总之 flush
之后,hibernate 会将这些更改写入 DB。
因此,当您评论 entityManager.flush();
时,hibernate 不会将更改与数据库同步(写入),导致它抱怨 group_table 中主键的重复条目。所以在这种情况下有必要调用 flush
。
注意:flush()
可能有助于在正在进行的事务和最终 commit
更改之间持久保存数据。因此,如果之后出现问题,您也可以回滚之前的更改,例如批处理 insert/update.
为了避免通过 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
没有attribute/s指定显式,只表示relationship/s私有属性与实体中的所有其他属性一起被克隆。
如果 CopyGroup
但是,指定至少一个属性 显式 (使用 CopyGroup#addAttribute()
), then the default depth level is CopyGroup.CASCADE_TREE
仅复制 attribute/s 指定的 addAttribute()
.
CopyGroup#setShouldResetPrimaryKey(true)
- Set if the primary
key should be reset to null.
CopyGroup#setShouldResetVersion(true)
- Set if the version
should be reset to null.
补充:
如果CopyGroup#setShouldResetVersion(true)
(带true
)与CASCADE_PRIVATE_PARTS
、CASCADE_ALL_PARTS
或NO_CASCADE
中的任何一个一起使用,则主键attribute/s 将不会设置克隆对象。
如果 CopyGroup#setShouldResetVersion(false)
(与 false
)与 CASCADE_TREE
一起使用,则主键 attribute/s 将是 copied/set。否则,如果给定 true
(使用 CASCADE_TREE
),则不会设置未使用 CopyGroup#addAttribute()
指定的主键 attribute/s(除非显式指定,即需要要明确)。
有关 CopyGroup
的更多详细信息,请参阅以下 link。
我有一个非常特殊的情况,我需要更新 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_id
和group_id
一起构成复合主键。 group_table
中的 group_id
是引用 user_role_table
中的 user_id
的外键。 GroupTable
持有来自 @Embeddable
class、GroupTablePK
.
@EmbeddedId
很少需要此信息。因此,我不会发布涉及的实体 class。
尝试通过首先删除提供的实体 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();
?这是到数据库的额外往返,应该避免。
Hibernate 文档说,
Flushing is the process of synchronizing the underlying persistent store with persistable state held in memory.
因此 flush()
会将您的持久状态(在您的情况下通过调用 delete(groupTable) 删除 groupTable
)与底层数据库同步。总之 flush
之后,hibernate 会将这些更改写入 DB。
因此,当您评论 entityManager.flush();
时,hibernate 不会将更改与数据库同步(写入),导致它抱怨 group_table 中主键的重复条目。所以在这种情况下有必要调用 flush
。
注意:flush()
可能有助于在正在进行的事务和最终 commit
更改之间持久保存数据。因此,如果之后出现问题,您也可以回滚之前的更改,例如批处理 insert/update.
为了避免通过 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
没有attribute/s指定显式,只表示relationship/s私有属性与实体中的所有其他属性一起被克隆。
如果 CopyGroup
但是,指定至少一个属性 显式 (使用 CopyGroup#addAttribute()
), then the default depth level is CopyGroup.CASCADE_TREE
仅复制 attribute/s 指定的 addAttribute()
.
CopyGroup#setShouldResetPrimaryKey(true)
- Set if the primary key should be reset to null.CopyGroup#setShouldResetVersion(true)
- Set if the version should be reset to null.
补充:
如果CopyGroup#setShouldResetVersion(true)
(带true
)与CASCADE_PRIVATE_PARTS
、CASCADE_ALL_PARTS
或NO_CASCADE
中的任何一个一起使用,则主键attribute/s 将不会设置克隆对象。
如果 CopyGroup#setShouldResetVersion(false)
(与 false
)与 CASCADE_TREE
一起使用,则主键 attribute/s 将是 copied/set。否则,如果给定 true
(使用 CASCADE_TREE
),则不会设置未使用 CopyGroup#addAttribute()
指定的主键 attribute/s(除非显式指定,即需要要明确)。
有关 CopyGroup
的更多详细信息,请参阅以下 link。