Hibernate Envers 6 + Jakarta Data

Hello,
Some background… migrating jakarta appication from

  • hibernate 5->hibernate 6
  • deltaspike → jakarta data

Application uses Envers to audit entities.
Jpamodelgen generates Repositories implementation with StatelessSession

	@PostConstruct
	private void openSession() {
		session = sessionFactory.unwrap(SessionFactory.class).openStatelessSession();
	}
	

and the problem is when entity is saved StatelessSessionImple fires event:

PreInsertEvent event = new PreInsertEvent(entity, id, state, persister, (EventSource)null);

and then:

public class EnversPostInsertEventListenerImpl extends BaseEnversEventListener implements PostInsertEventListener {
	public EnversPostInsertEventListenerImpl(EnversService enversService) {
		super( enversService );
	}

	@Override
	public void onPostInsert(PostInsertEvent event) {
		final String entityName = event.getPersister().getEntityName();

		if ( getEnversService().getEntitiesConfigurations().isVersioned( entityName ) ) {
			checkIfTransactionInProgress( event.getSession() );

all fails with error:

Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.SessionImplementor.isTransactionInProgress()" because "session" is null
        at org.hibernate@6.6.3.Final//org.hibernate.envers.event.spi.BaseEnversEventListener.checkIfTransactionInProgress(BaseEnversEventListener.java:134)
        at org.hibernate@6.6.3.Final//org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl.onPostInsert(EnversPostInsertEventListenerImpl.java:34)
        at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.firePostInsert(StatelessSessionImpl.java:380)
        at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:160)
        at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:118)

Is there some solution for this ?

This sounds like a bug in the envers listener, please create a project based on our test case templates that shows the steps needed to reproduce this problem and attach it to a new issue in our tracker.

I doubt that Envers ever worked with StatelessSession.

But on the other hand I guess StatelessSession didn’t used to raise PreInsertEvents, which explains why you started getting NPEs just now.

Ideally, I think, Envers would use its own StatelessSession for persistence operations when the event doesn’t carry a stateful session.

Whatever, an NPE is always a bug, as Marco says.

On 5.x StatelessSession didn’t raise PreInsertEvent. Deltaspike uses stateful Session so Envers works correct.
On jpamodelgen there is no option to use stateful Session

I hope it is OK that I created a project that shows the steps needed to reproduce this problem based on WildFly todo-backend quickstart, not the Hibernate test case template. Here it is: Jakarta Data Hibernate Envers 6.6.3 integration bug.

The project contains the Envers @Audited ToDoEntity and Jakarta Data Repository ToDoRepository (see code here). Saving the entity with ToDoRepository.insert() fails with the following error:

13:16:07,660 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request POST / - org.wildfly.quickstarts.todos.ToDoController.addTodo: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.SessionImplementor.isTransactionInProgress()" because "session" is null
	at org.hibernate@6.6.3.Final//org.hibernate.envers.event.spi.BaseEnversEventListener.checkIfTransactionInProgress(BaseEnversEventListener.java:134)
	at org.hibernate@6.6.3.Final//org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl.onPostInsert(EnversPostInsertEventListenerImpl.java:34)
	at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.firePostInsert(StatelessSessionImpl.java:380)
	at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:160)
	at org.hibernate@6.6.3.Final//org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:118)
	at deployment.ROOT.war//org.wildfly.quickstarts.todos.ToDoRepository_.insert(ToDoRepository_.java:48)
	at deployment.ROOT.war//org.wildfly.quickstarts.todos.ToDoRepository_$Proxy$_$$_WeldClientProxy.insert(Unknown Source)
	at deployment.ROOT.war//org.wildfly.quickstarts.todos.ToDoController.addTodo(ToDoController.java:92)

So the problem is that there is currently no way to use Envers audited entities with Jakarta Data repositories.

You need Maven, Java 17 JDK and Docker to build and run the application locally.
Here are step-by-step instructions for reproducing the bug:

  1. Clone the repository:
git clone https://gitlab.com/mrts/jakarta-data-hibernate-envers-6.6.3-integration-bug.git
cd jakarta-data-hibernate-envers-6.6.3-integration-bug
  1. Package the backend:
mvn clean package -Pprovisioned-server
  1. Run a local PostgreSQL database with Docker:
docker run --rm --name todo-backend-db \
    -e POSTGRES_USER=todos \
    -e POSTGRES_PASSWORD=mysecretpassword \
    -p 5432:5432 postgres
  1. Run the application, passing database connection parameters in environment variables:
./target/server/bin/standalone.sh \
    -Denv.POSTGRESQL_DATABASE=todos \
    -Denv.POSTGRESQL_DATASOURCE=ToDos \
    -Denv.POSTGRESQL_SERVICE_HOST=localhost \
    -Denv.POSTGRESQL_SERVICE_PORT=5432 \
    -Denv.POSTGRESQL_USER=todos \
    -Denv.POSTGRESQL_PASSWORD=mysecretpassword
  1. Run the integration test that uses the HTTP client against the local server HTTP API:
mvn verify -Pintegration-testing -Dserver.host=http://localhost:8080

Additionally, the following error is reported during the application deployment:

13:10:48,564 ERROR [org.hibernate.metamodel.internal.MetadataContext] (ServerService Thread Pool -- 46) HHH015007: Illegal argument on static metamodel field injection : org.hibernate.envers.DefaultRevisionEntity_#class_; expected type :  org.hibernate.metamodel.model.domain.internal.EntityTypeImpl; encountered type : jakarta.persistence.metamodel.MappedSuperclassType

Should this be reported as a separate Jira issue?


I wasn’t able to login to Jira, so I couldn’t attach the detailed problem report to the Jira issue. @mbladel, could you please add it there? Thanks in advance for looking into this!

@mbladel, I noticed that you added a comment to Jira that a test case to reproduce exists here, thanks!

However, I see that the issue status is still ‘Awaiting response’. Is there anything else I can do to help this forward?

I haven’t looked at your reproducer enough to verify it’s pertinent with the issue - also the problem was identified to be more generic than just Jakarta Data repositories, affecting Stateless Session in general.

If someone can confirm your reproducer is enough, or produce a simpler version of your test case using our standard templates, the status of the issue will be updated.

Thanks for the response.

Would integrating Jpamodelgen into the test case template project be straightforward? If so, I can explore this further.

The core issue seems to be that Envers is currently fundamentally incompatible with how Jpamodelgen generates Jakarta Data Repository implementations, so the key challenge is how to align Envers with Jakarta Data. Whether the session should be stateful or stateless seems to be an internal implementation detail.

To be absolutely clear, this simple, standard Jakarta Data Repository and Envers code (linked here) fails with the reported error:

@Audited
@Entity
public class ToDo {
    ...
}

@Repository
public interface ToDoRepository {
    ...

    @Insert
    void insert(ToDo todo);
}

toDoRepository.insert(todo);

I would greatly appreciate it if you or someone on the team could validate the reproducer to confirm that it accurately reproduces the issue. I can assure that no additional steps are required beyond the five listed above.

If this is sufficient to update the Jira issue status beyond ‘Awaiting response,’ that would be great. However, if anything is missing from the reproducer that is preventing progress, please let me know, and I’d be happy to refine it further.

Thanks for looking into this!

Should be as easy as copy-pasting your entity mappings and jakarta data repository there.

No, the issue is exactly that Hibernate Envers does not support StatelessSession, regardless of whether it is used directly by the user or if it’s coming from Jakarta Data. That’s why I’m saying the Jira has a broader scope than just repositories.

@mbladel can option with statefull session in jpamodelgen can be added?

1 Like

@mbladel, would the following test case in org.hibernate.orm.test.envers test suite be sufficient:

package org.hibernate.orm.test.envers;

@Jpa(annotatedClasses = StatelessSessionInsertTest.ToDo.class)
public class StatelessSessionInsertTest {

	@Test
	public void test(EntityManagerFactoryScope scope) {
		scope.inEntityManager(entityManager -> {
			Session session = entityManager.unwrap(Session.class);
			SessionFactory sessionFactory = session.getSessionFactory();

			try (StatelessSession statelessSession = sessionFactory.openStatelessSession()) {
				ToDo todo = new ToDo();
				todo.setId(1L);
				todo.setTask("Reproduce the Envers session is null error");
				statelessSession.insert(todo);
			}
		});
	}

	@Entity
	@Audited
	public static class ToDo {
		@Id
		private Long id;

		private String task;

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public String getTask() {
			return task;
		}

		public void setTask(String task) {
			this.task = task;
		}
	}
}

This fails with the expected Cannot invoke "org.hibernate.engine.spi.SessionImplementor.isTransactionInProgress()" because "session" is null error (insert() triggers the Envers event listener, which tries to call session.isTransactionInProgress() where session is null).

If this is sufficient, I can submit a pull request to GitHub - hibernate/hibernate-orm: Hibernate's core Object/Relational Mapping functionality.

@mrts sure, feel free to open a PR and link it to the opened Jira or simply paste the test code there.

Here we go:

My Jira access started working, so I was able to add a comment with the PR link under the Jira issue. I hope that the issue is ready for the next step now.

I can see the following three issues related to Envers and Jakarta Data currently:

  1. NullPointerException in Envers that occurs during StatelessSession.insert() of an entity that has the Envers @Audited annotation. This issue is well covered in the discussion above and the test in the pull request, but as Gavin mentioned, this is a symptom of a deeper problem: Envers was never intended to be used with stateless sessions. A decision is needed whether Envers should support stateless sessions or not. If the support is added, it might be a long and complex task.
  2. As the Hibernate metamodel generator generates Jakarta Data Repository implementations using stateless sessions, Envers is not currently compatible with Jakarta Data Repositories because of the previous problem. Using stateless sessions in Jakarta Data Repository implementation is a bold choice, have the implications of this been previously discussed in the team, is there a design document perhaps that discusses the tradeoffs? Apart from Envers, other systems and applications may be affected. Perhaps it would be possible to add a metamodel generator configuration option that generates code that uses stateful sessions instead as @Michal_Filipczak suggested? DeltaSpike Data has a battle-tested implementation that can be used as a blueprint, so adding this should be relatively easy. This would make Jakarta Data Repositories compatible with Envers and possibly solve problems in other systems.
  3. The metamodel generator seems to generate injection code that expects EntityTypeImpl but instead gets MappedSuperclassType for Envers entities during runtime. The error is
ERROR [org.hibernate.metamodel.internal.MetadataContext] (ServerService Thread Pool -- 46) HHH015007: Illegal argument on static metamodel field injection : org.hibernate.envers.DefaultRevisionEntity_#class_; expected type :  org.hibernate.metamodel.model.domain.internal.EntityTypeImpl; encountered type : jakarta.persistence.metamodel.MappedSuperclassType

How do you see these issues and what do you think the next steps should be?