使用JPA

时间:2015-07-18 13:46:56

标签: hibernate jpa ejb eclipselink

我有一个非常特殊的情况,我需要更新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_idgroup_id一起形成复合主键。 group_id中的group_tableuser_id中引用user_role_table的外键。 GroupTable保留@EmbeddedId班级中的@EmbeddableGroupTablePK

很少需要此信息。因此,我不会发布涉及的实体类。

尝试通过首先删除提供的实体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();?这是对数据库的额外往返,应该避免。

2 个答案:

答案 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_PARTSCASCADE_ALL_PARTSNO_CASCADE中的任何人一起使用,则主要关键字属性为克隆的对象将不会被设置。

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

有关CopyGroup的更多详细信息,请参阅以下链接。