12

I am using spring boot and spring data jpa. I am also using hibernate envers and I need access to AuditReaderFactory so that I can write Audit Queries.

Since, its a spring boot and spring data jpa, everything is auto configured. So when I do this,

@Autowired
AuditReaderFactory auditReaderFactory;

It doesn't work. I get the following error.

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.hibernate.envers.AuditReaderFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency

How do I get a proper reference to AuditReaderFactory in my repository classes?

ajm
  • 12,863
  • 58
  • 163
  • 234

5 Answers5

12

Create configuration class such as AuditConfiguration.java:

import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManagerFactory;

@Configuration
public class AuditConfiguration {

    private final EntityManagerFactory entityManagerFactory;

    AuditConfiguration(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    @Bean
    AuditReader auditReader() {
        return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
    }
}

After that you can autowire AuditReader in your component classes.

deniss-s
  • 235
  • 2
  • 8
  • 1
    Your answer will cause creation of singleton alike bean, that can cause connections being not closed correctly which can fully stop the service to be able to fetch anything from the database (an issue like this one: https://stackoverflow.com/questions/75230651/connection-is-closed-with-data-envers). The correct answer is the one supplied by @Guilio Pulina. – Dmitriy Jul 12 '23 at 08:05
6

In my opinion, @deniss-s answer (top-voted one) is not correct because EntityManager must not be reused outside of the context of a transaction (see https://stackoverflow.com/a/9375891/554117), while the solution makes use of it as a singleton.

A correct retrieval of AuditReader looks like this:

public class AuditRepository {

    @PersistenceContext
    private EntityManager entityManager;

    private AuditReader getAuditReader() {
        return AuditReaderFactory.get(entityManager);
    }
 
     public Optional<T> getRevision() {
         final AuditReader auditReader = getAuditReader();
         ...
    }

}

EDIT: I just realized that @deniss-s updated his answer (likely after reading mine), but his answer is still incorrect: AuditReader is still created as a singleton, while a new instance must be created each time.

Giulio Pulina
  • 344
  • 4
  • 15
5

Following up on above answer, and linked question, I found this to work. (Would be nice if this can be turned into an autowire of the reader directly somehow)

@Autowired
private EntityManagerFactory factory;

public void stuff() {
    AuditReader audit = AuditReaderFactory.get(factory.createEntityManager());
}
Jay
  • 3,203
  • 2
  • 25
  • 31
2

AuditReaderFactory only has two static methods. Can you autowire a SessionFactory object or your EntityMananger? Looks like either would give you what you want, which is access to an AuditReader.

AuditReaderFactory.get(sessionFactory.getCurrentSession())

EDIT this post has some detail or wiring SessionFactory if needed

Community
  • 1
  • 1
heisbrandon
  • 1,180
  • 7
  • 8
  • Following linked, to autowire SessionFactory, this didn't work; Can create and save entities, still can't get AuditReaderFactory: (spring 1.5, hibernate 5.2.1) org.hibernate.HibernateException: No CurrentSessionContext configured! – Jay Jun 05 '18 at 16:45
1

I can't create a comment, so I'll duplicate Giulio Pulina's answer. His answer is indeed more correct than those with more upvotes. Other answers are also working, but only as long as there is a connection to the database. In case of loss of connection with the database (restart of the container with the database), AuditReader becomes inoperable. Therefore, the current response, when the AuditReader is created before each request, is most preferable.

A correct retrieval of AuditReader looks like this:

public class AuditRepository {

    @PersistenceContext
    private EntityManager entityManager;

    private AuditReader getAuditReader() {
        return AuditReaderFactory.get(entityManager);
    }
 
     public Optional<T> getRevision() {
         final AuditReader auditReader = getAuditReader();
         ...
    }
}
Dmitrii
  • 33
  • 1
  • 8
  • 1
    Loss of connection can (and will) happen when using a connection pool with a finite connection TTL. In the configuration proposed in the accepted answer, the AuditReader is a singleton (shared by all the threads of the application) and is tied to a specific database connection of the pool. When the application is not accessed for a period of time (e.g. during the night), the database connection expires, but it's still referenced in the AuditReader: this will make AuditReader not to function properly and throw a lot of exceptions. – Giulio Pulina Apr 18 '23 at 08:17