0

I have a very special case where I need to update a primary key which is disallowed by JPA (EclipseLink 2.6.0). Therefore, the entity is first deleted and then inserted with new values.

The tables involved have a predefined structure being required by GlassFish Server for JAAS authentication.

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 and group_id together form a composite primary key. group_id in group_table is a foreign key referencing user_id in user_role_table. GroupTable holds an @EmbeddedId from an @Embeddable class, GroupTablePK.

This information will seldom be needed. Therefore, I am not posting the entity classes involved.


An update is attempted to be simulated by first removing the supplied entity, GroupTable and then persisting the same entity using a new value of group_id as follows (in an EJB using CMT).

Again, this is a very special case and even updating a user's authority is fairly rare. Just that it is worth providing the functionality beforehand.

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);
}

These methods are executed in the same transaction and they do their job pretty well but only if the only commented line inside the update() method is uncommented. Otherwise, it complains about a duplicate entry for a primary key in the group_table - the entity which is to be removed first is not removed prior to persisting that entity causing a duplicate insert to spawn.

Why is entityManager.flush(); required prior to persisting the entity? It is an additional round trip to the database and should be avoided.

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • It is required because otherwise, persist cancels out your delete call, so you are just changing the primary key. Changing a primary key is not allowed in JPA, which is why you are trying to work around it with the delete/persist. It really would be better for you to clone the entity and then update the clone's pk. EclipseLInk has clone methods and policies to help accomplish this, and using a clone more accurately represents what you are doing - identity is fundamental to an entity and so a new identity requires a new entity. – Chris Jul 18 '15 at 21:12
  • This is somewhat not as intuitive as it looks like as the delete call is suspended even though a detached instance is attempted to persist after delete. Looks something like [this](http://stackoverflow.com/q/30996141/1391249). – Tiny Jul 19 '15 at 11:32

2 Answers2

2

Hibernate docs says,

Flushing is the process of synchronizing the underlying persistent store with persistable state held in memory.

So flush() will synchronize your persistent state (in your case it is deleted groupTable by calling delete(groupTable)) with underlying database. In shorts after flush hibernate will write these changes in DB.

So when you comment entityManager.flush(); hibernate won't synchronize (write) the changes with database, resulting it complains about a duplicate entry for a primary key in the group_table. So it's necessary to call flush in this case.

Note: flush() may be useful to persist the data in between the ongoing transaction & then finally commit the changes. So you can also rollback the previous changes if there occurs some problem afterwards, like for batch insert/update.

Amogh
  • 4,453
  • 11
  • 45
  • 106
0

In order to avoid an additional database round trip through EntityManager#flush();, I used EclipseLink specific CopyGroup to clone the specified entity before persisting it to the database as suggested by Chris in the comment section below the question. Such as,

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;
}

The delete() method as shown in the question is left untouched.

CopyGroup.CASCADE_PRIVATE_PARTS is the default depth level, If CopyGroup has no attribute/s specified explicitly, indicating only the relationship/s being privately owned are cloned along with all other attributes in the entity.

If CopyGroup however, specifies at least one attribute explicitly (using CopyGroup#addAttribute()), then the default depth level is CopyGroup.CASCADE_TREE which only copies the attribute/s as specified by 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.


Additional :

If CopyGroup#setShouldResetVersion(true) (with true) is used along with anyone of CASCADE_PRIVATE_PARTS, CASCADE_ALL_PARTS or NO_CASCADE, then the primary key attribute/s of the cloned object will not be set.

If CopyGroup#setShouldResetVersion(false) (with false) is used along with CASCADE_TREE, then the primary key attribute/s will be copied/set. Otherwise, if it is given true (using CASCADE_TREE), then the primary key attribute/s which are not specified with CopyGroup#addAttribute() will not be set (unless explicitly specified i.e. one needs to be explicit).

More details about CopyGroup can be found in the following link.

Tiny
  • 27,221
  • 105
  • 339
  • 599