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?

@mbladel, any thoughts on this? Just wanted to follow up and see if there’s any update on how the team views these issues and potential next steps. Let me know if there’s anything I can do to help move this forward!

I’ve accepted the issue on Jira since you provided a reproducer, and it’s now awaiting contribution. The issue priority will be evaluated by the Hibernate together with all other pending issues, and if it will be considered high priority or someone from the team is interested in working on it it will be picked up.

We are a small team, and there are a lot of open issues. Community contributions are always welcome, of course, so if you want to speed this up I suggest trying to contribute a fix to Hibernate yourself.

Thanks, @mbladel! I understand the team’s workload and truly admire your work.

I’m interested in contributing, but I need some input before proceeding.

First, as Hibernate supporting StatelessSession in Envers is a fundamental change, an official design decision is needed how to implement this. Would the suggested approach, where Envers would use its own StatelessSession for persistence operations when the event doesn’t carry a stateful session, be the way to go?

To me, adding an option to the metamodel generator to generate Jakarta Data Repository implementations using Stateful Sessions looks like a safer and more contained change. But before working on it, I would need team approval and a separate Jira issue to track it. Would this be an acceptable direction?

Thanks for looking into this!

@mrts if you wish to discuss design aspects before starting to work on a prototype, you’re very welcome to join our zulip chat channels and ask for feedback on your ideas.

For this part especially, please use Zulip, and ping Gavin King, who I know has plans to evolve Jakarta Data or Hibernate Repositories, possibly to allow using “stateful” sessions. But I’m not sure those plans are short-term. So yes, talk to Gavin :slight_smile:

Thanks for the tip, I’ll continue the discussion in Zulip.