Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate entity property not being persisted to the database, despite flush and transaction commit

I have an Entity MXGroup which is logically deletable via a status property. This entity is persisted into a MySQL database via Hibernate. When I create this entity via my JSF page, persist it, then attempt to update this entity's status column without reloading my page in between creation and logically deleting it, the change to the status column does not get persisted.

The entity is clearly in Hibernate's cache and the database. Updating the property and then calling merge with it doesn't merge the changes into the database.

I've explicitly called flush and getTransaction().commit() on my entity manager after merging the entity, but still no dice.

If I refresh my page (Probably getting a different EntityManager as my EntityManagers are conversation scoped) then I can suddenly delete my MXGroup without any issue.

I'm aware there are workarounds that I could do - so this is largely an academic exercise as to why this is happening...

My MXGroup entities are loaded via a RESTful interface via ajax with the following methods: (PersonalGroup entity extends MXGroup entity)

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("personal/")
public Response restPersonalGroups() {
    return Response.ok().entity(serializePersonalGroups()).build();
}

public String serializePersonalGroups() {
    final List<PersonalGroup> personalGroupsList = this.userGroupService.getPersonalGroups();
    String personalGroups = serializeGroupList(personalGroupsList);
    return personalGroups;
}

With the userGroupService.getPersonalGroups() method looking like:

public List<PersonalGroup> getPersonalGroups() {
    CriteriaBuilder builder = this.getEntityManager().getCriteriaBuilder();
    CriteriaQuery<PersonalGroup> criteria = builder.createQuery(PersonalGroup.class);
    Root<PersonalGroup> root = criteria.from(PersonalGroup.class);

    List<Predicate> predicateList = new ArrayList<Predicate>();
    predicateList.addAll(Arrays.asList(getCommonPredicates(PersonalGroup.class, builder, criteria, root)));
    predicateList.add(builder.equal(root.get(PersonalGroup_.user), getUser()));

    TypedQuery<PersonalGroup> query = this.getEntityManager().createQuery(
        criteria.select(root)
            .where(predicateList.toArray(new Predicate[predicateList.size()]))
            .orderBy(builder.asc(root.get(MXGroup_.name)))
            .distinct(true));

    List<PersonalGroup> personalGroups = query.getResultList();
    for (PersonalGroup pg : personalGroups) {
        this.getEntityManager().refresh(pg);
    }
    return personalGroups;
}

getCommonPredicates in this circumstance only adds a predicate checking if status == 1

The delete functionality looks like:

public void deletePersonalGroup() throws BadLoginNameException, UniqueUserLoginException {
    String groupIdStr = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("groupId");
    int groupId = Integer.parseInt(groupIdStr);
    MXGroup group = this.userGroupService.findMXGroup(groupId);

    group.setStatus(0);
    this.userGroupService.mergeMXGroup(group);
    this.userGroupService.forceCommit();
}

with the mergeMXGroup being:

@Override
public void mergeMXGroup(MXGroup group) {
    super.merge(group);
}

whose super implementation looks like:

protected <T> T merge(T entity) {
    if (canUseService() && beforeMergeEntity(entity)) {
        T merge = this.getEntityManager().merge(entity);
        return merge;
    }
    return null;
}

And finally I force a flush and a commit like this:

public void forceCommit() {
    this.getEntityManager().getTransaction().commit();
    this.getEntityManager().flush();
}    

The RESTful interface will almost certainly have a different entity manager to the delete code as the delete code is CDI managed and the RESTful interface is EJB. But I would have thought the flush and commit would have solved the issue? In fact this is the first time I've had to explicitly flush / commit in this entire project...

Any thoughts?

like image 833
Matt Fellows Avatar asked Oct 23 '15 07:10

Matt Fellows


1 Answers

I'm reviewing the spec with regard to the semantics of the merge operation.

Either one of these scenarios could possibly explain the cause of your issue (or perhaps not):

The semantics of the merge operation applied to an entity X are as follows:

  • If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created.
  • If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.
  • If X is a removed entity instance, an IllegalArgumentException will be thrown by the merge operation (or the transaction commit will fail).
  • If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been annotated with the cascade element value cascade=MERGE or cascade=ALL annotation.
  • If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y

The persistence provider must not merge fields marked LAZY that have not been fetched: it must ignore such fields when merging.

like image 154
Ish Avatar answered Oct 04 '22 23:10

Ish