Hibernate uses old closed SessionImpl instead of given SessionImpl


#1

I have a problem with Hibernate Enverse (Version 5.2.0-Final).

Context: I’m auditing some entities with some lazy relations. I have a jsf-page that loads one version of one entity with all relations of that version. That works fine. So now I have a page that shows a revision of the entity with all relations of that revision. On this page I can open a fieldset, that triggers an AJAX. In this request we reattach all relations by calling entityManager.merge(entity) to be able to fetch the lazy relations in this fieldset.

The Problem: The AJAX is a new request. The server calls entityManager.merge(entity) , what enforces creation of a new EntityManager (So a new org.hibernate.internal.SessionImpl is created). On this object hibernate calls SessionImpl.merge(...) . But in the method org.hibernate.internal.AbstractSharedSessionContract.createQuery(String) a other SessionImpl object is used, which is already closed. That enforces an java.lang.IllegalStateException: Session/EntityManager is closed .

In one sentence : Although a new entityManager was created and a merge was called on that new entityManger, Hibernate uses an old Session/EntityManager of the request before.

I debugged the problem and found following:

  • Debug1: Shows the Stacktrace of the SessionImpl.merge with the session’s object id
  • Debug2: Shows the last method with the correct SessionImpl object (see it’s id). This object is not used in next methods.
  • Debug3: The step after Debug2 does not know the given SessionImpl object. It has it’s own SessionImpl object in collection.initializor.versionsReader . This session was created and closed in the request before (on loading the page).
  • Debug4: Now Hibernate wants to create the query wit the closed SessionImpl
  • Debug5: This enforces the exception, as the session is closed.

My questions :

  • Is this a bug of Hibernate?
  • Why is the given SessionImpl in method org.hibernate.type.CollectionType.getElementIterater(...) not used?
  • Anyone knows a solution or workaround for this problem?

Tank you very much for any idea. I spent days on this bug.


#2

That can only happen if your application uses multiple Session Objects.

  • Is this a bug of Hibernate?

If you can replicate the issue with this test case template, then it’s a bug.

  • Why is the given SessionImpl in method org.hibernate.type.CollectionType.getElementIterater(...) not used?

I’ll check it out once you provide the replicating test case.


#3

I gave a pretty lengthy answer your SO post on the same topic; I’ll replicate the important points specific to Envers in which @vlad didn’t cover earlier which I think is important in case someone else stumbles onto this here.

What your post eludes to is the following behavior

// Request 1
// You use the current entity manager to get the MyEntity from AuditReader
// You cache this instance somewhere so you can get it in the next request
someSessionBean.setMyEntity( getAuditedMyEntity() );

// Request 2
MyEntity mergedInstance = getEm().merge( someSessionBean.getMyEntity() );
for ( SomeCollectionItem item : mergedInstance.getSomeCollection() ) {
  // do something with item here
}

You cannot do this with objects returned from the Envers Query API mainly because those objects are to be considered potentially partial snapshots of the data at a point in time, nothing more.

In order to understand what that means, lets dive into an example:

@Entity
public class MyEntity {
  @Id
  @GeneratedValue
  private Integer id;
  @Audited
  private String data;
  @Audited
  private List<SomeCollectionItem> items;
  private Integer someValue;
}

In this example, the only attributes that Envers will audit is the id, data, and items collection. The actual someValue attribute will be skipped because the entity mapping did not designate it to be audited.

There are potentially a couple outcomes of calling merge on this instance.

  1. Nothing happens (which would be a good thing)
  2. The operation throws an exception (which would be a good thing)
  3. The operation succeeds (which is bad).

The main reason why (3) is bad is because type inference.

Lets assume your MyEntity in your main table had a someValue of 10 in your database. If you execute merge on the audit entity instance returned from the Envers Query API, you will basically force Hibernate to update that column to null. This is because Hibernate will use type inference to translate MyEntity.class to the real entity-type mapping, not the MyEntity_AUD map-mode mapping that Envers generated during bootstrap.

This means the wrong table gets updated due to the merge. That isn’t considered a bug, its a mistake in how you’re using the objects to perform persistence operations.

So in short, don’t do that :slight_smile: .

You basically have 2 options here:

  1. Fully initialize the audit entity instance with the first session when you fetch it. This means asking each collection to initialize itself before you close the session. Under the hood the proxy will be flagged as initialized and not require the session when you access it again.
  2. Query the audited entity instance in the second session via primary-key and revision number rather than using the cached copy from the prior request.

We can look at an enhancement to Envers that would permit a reassociation of an audited entity instance to a new AuditReader instance, much like what EntityManager#merge provides for JPA but that is not behavior that is currently available or supported.


#4

Thank you very much. Now I reload the audited Entity by it’s PRIMARY-KEY and the revision number. That works fine.