我有一个非常特殊的情况,我需要更新JPA(EclipseLink 2.6.0)不允许的主键。因此,首先删除实体,然后插入新值。
所涉及的表格具有GlassFish Server for 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_id
中的group_table
是user_id
中引用user_role_table
的外键。 GroupTable
保留@EmbeddedId
班级中的@Embeddable
,GroupTablePK
。
很少需要此信息。因此,我不会发布涉及的实体类。
尝试通过首先删除提供的实体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();
?这是对数据库的额外往返,应该避免。
答案 0 :(得分:2)
Hibernate docs说,
刷新是同步底层持久性的过程 持久存在的商店。
所以flush()
将通过调用delete(groupTable))与底层数据库同步你的持久状态(在你的情况下它被删除groupTable
)。在flush
hibernate之后,将在DB中写下这些更改。
因此,当您评论entityManager.flush();
时,hibernate不会与数据库同步(写入)更改,从而导致它抱怨group_table中主键的重复条目。因此,在这种情况下调用flush
是必要的。
注意:flush()
可能有助于在正在进行的交易和广告之间保留数据。然后最后commit
更改。因此,如果之后出现问题,您也可以回滚以前的更改,例如批量插入/更新。
答案 1 :(得分:0)
为了避免额外的数据库往返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
,仅复制属性/ s由addAttribute()
指定。
CopyGroup#setShouldResetPrimaryKey(true)
- 设置是否为主要版本 key应重置为null。CopyGroup#setShouldResetVersion(true)
- 设置版本 应该重置为null。
其他:
如果CopyGroup#setShouldResetVersion(true)
(包含true
)与CASCADE_PRIVATE_PARTS
,CASCADE_ALL_PARTS
或NO_CASCADE
中的任何人一起使用,则主要关键字属性为克隆的对象将不会被设置。
如果CopyGroup#setShouldResetVersion(false)
(带有false
)与CASCADE_TREE
一起使用,则会复制/设置主键属性。否则,如果给它true
(使用CASCADE_TREE
),则不会设置未使用CopyGroup#addAttribute()
指定的主键属性(除非明确指定,即需要显式的)。
有关CopyGroup
的更多详细信息,请参阅以下链接。