javahibernatespring-data-jpahibernate-envers

How to inject spring beans into the hibernate envers RevisionListener


I am using Spring 5.1 and Hibernate 5.3.9 including hibernate-envers. I don't know how to inject spring beans into the hibernate envers custom RevisionListener.

I have tried

  (@Service or @Component)
  public class ExtendedRevisionListener implements RevisionListener {

  @Autowired
  private MyService myService;

  void newRevision(Object revisionEntity){
    myService.doSomething(...)
  }
}

Of course the class is included in the @ComponentScan packages resolution. A problem is that myService is not injected into the listener.

In the hibernate-envers documentation:

https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers-basics

As of Hibernate Envers 5.3, dependency injection is now supported for a RevisionListener. This feature is up to the various dependency frameworks, such as CDI and Spring, to supply the necessary implementation during Hibernate ORM bootstrap to support injection. If no qualifying implementation is supplied, the RevisionListener will be constructed without injection.

Unfortunatelly i haven't found any working example.

@Naros I do a setup of the EntityMaganerFactoryBean in my spring persistence JPA configuration -> entityManager = new org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean()

10:49| DEBUG | SessionFactoryImpl.java 252 | Session factory constructed with filter configurations : {}
10:49| DEBUG | SessionFactoryImpl.java 253 | Instantiating session factory with properties: {hibernate.format_sql=true, awt.toolkit=sun.awt.windows.WToolkit, hibernate.id.new_generator_mappings=false, java.specification.version=1.8, logging.configuration=file:C:\wildfly16\standalone\configuration\logging.properties, sun.cpu.isalist=amd64, sun.jnu.encoding=Cp1250, sun.arch.data.model=64, org.jboss.resolver.warning=true, java.vendor.url=http://java.oracle.com/, javax.persistence.validation.mode=AUTO, sun.boot.library.path=C:\Program Files\Java\jdk1.8.0_201\jre\bin, org.jboss.logmanager.nocolor=true, sun.java.command=org.jboss.modules.Main -mp C:\wildfly16\modules org.jboss.as.standalone -b localhost --server-config=standalone.xml -Djboss.server.base.dir=C:\wildfly16\standalone, java.specification.vendor=Oracle Corporation, java.naming.factory.url.pkgs=org.jboss.as.naming.interfaces, java.home=C:\Program Files\Java\jdk1.8.0_201\jre, jboss.server.persist.config=true, file.separator=\, jboss.server.data.dir=C:\wildfly16\standalone\data, line.separator=
, java.vm.specification.vendor=Oracle Corporation, java.specification.name=Java Platform API Specification, jboss.server.base.dir=C:\wildfly16\standalone, hibernate.transaction.coordinator_class=class org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl, wicket.configuration=development, jboss.bind.address.management=localhost, sun.boot.class.path=C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\classes, hibernate.hbm2ddl.auto=update, user.script=, java.protocol.handler.pkgs=org.jboss.net.protocol|org.jboss.vfs.protocol, sun.management.compiler=HotSpot 64-Bit Tiered Compilers, java.runtime.version=1.8.0_201-b09, user.name=ptaszek, hibernate.enable_lazy_load_no_trans=true, file.encoding=Cp1250, sun.rmi.dgc.client.gcInterval=3600000, java.io.tmpdir=C:\Users\ptaszek\AppData\Local\Temp\, org.jboss.boot.log.file=C:\wildfly16\standalone\log\boot.log, jboss.modules.system.pkgs=org.jboss.byteman, java.version=1.8.0_201, java.vm.specification.name=Java Virtual Machine Specification, jboss.bind.address=localhost, java.awt.printerjob=sun.awt.windows.WPrinterJob, jboss.host.name=1501-10, org.jboss.security.context.ThreadLocal=true, sun.os.patch.level=, module.path=C:\wildfly16\modules, java.library.path=C:\Program Files\Java\jdk1.8.0_201\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;native;C:/Program Files/Java/jre1.8.0_211/bin/server;C:/Program Files/Java/jre1.8.0_211/bin;C:/Program Files/Java/jre1.8.0_211/lib/amd64;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Program Files\Microsoft MPI\Bin\;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\AOMEI Backupper;C:\Program Files\TortoiseSVN\bin;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\;C:\Program Files\Microsoft SQL Server\140\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Binn\;C:\Program Files\Microsoft SQL Server\140\DTS\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\;C:\Program Files\MySQL\MySQL Shell 8.0\bin\;C:\eclipse;;., jboss.server.name=1501-10, java.vendor=Oracle Corporation, jboss.modules.dir=C:\wildfly16\modules, sun.io.unicode.encoding=UnicodeLittle, jboss.server.temp.dir=C:\wildfly16\standalone\tmp, sun.desktop=windows, file.encoding.pkg=sun.io, hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_HOLD, hibernate.dialect=org.hibernate.dialect.MySQL8Dialect, java.class.path=C:\wildfly16\jboss-modules.jar, jboss.server.deploy.dir=C:\wildfly16\standalone\data\content, java.vm.vendor=Oracle Corporation, user.variant=, user.timezone=Europe/Belgrade, os.name=Windows 10, java.vm.specification.version=1.8, program.name=JBossTools: WildFly 16 at localhost, hibernate.generate_statistics=false, sun.java.launcher=SUN_STANDARD, user.country=PL, hibernate.use_sql_comments=false, javax.persistence.sharedCache.mode=UNSPECIFIED, jboss.server.config.dir=C:\wildfly16\standalone\configuration, sun.cpu.endian=little, user.home=C:\Users\ptaszek, user.language=pl, jboss.qualified.host.name=1501-10, java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment, java.awt.headless=true, org.apache.xml.security.ignoreLineBreaks=true, sun.rmi.dgc.server.gcInterval=3600000, java.net.preferIPv4Stack=true, jboss.home.dir=C:\wildfly16, path.separator=;, os.version=10.0, java.endorsed.dirs=C:\Program Files\Java\jdk1.8.0_201\jre\lib\endorsed, java.runtime.name=Java(TM) SE Runtime Environment, hibernate.ejb.persistenceUnitName=default, sun.nio.ch.bugLevel=, java.vm.name=Java HotSpot(TM) 64-Bit Server VM, hibernate.show_sql=false, java.security.auth.login.config=jar:file:/C:/wildfly16/modules/system/layers/base/org/picketbox/main/picketbox-5.0.3.Final.jar!/auth.conf, jboss.server.log.dir=C:\wildfly16\standalone\log, java.vendor.url.bug=http://bugreport.sun.com/bugreport/, user.dir=C:\wildfly16\bin, os.arch=amd64, org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy, javax.management.builder.initial=org.jboss.as.jmx.PluggableMBeanServerBuilder, hibernate.boot.CfgXmlAccessService.key=org.hibernate.boot.cfgxml.spi.LoadedConfig@35a3f1ae, java.util.logging.manager=org.jboss.logmanager.LogManager, java.vm.info=mixed mode, java.vm.version=25.201-b09, hibernate.bytecode.use_reflection_optimizer=false, hibernate.connection.datasource=HikariDataSource (HikariCpConnectionPool), java.ext.dirs=C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext, jboss.node.name=1501-10, java.class.version=52.0}

In my PersistenceJPAConfig:

@EnableJpaRepositories(basePackages = "pl.atmoterm", entityManagerFactoryRef = "localContainerEntityManagerFactoryBean")

  @Bean
  @DependsOn({"dataSource"})
  public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() {
     LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
     em.setDataSource(dataSource());
     em.setPackagesToScan(new String[]{
        "pl.atmoterm.**.*"
     });

     em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
     em.setJpaProperties(additionalProperties());
     return em;
  }

Solution

  • There really is not much involved in setting up bean injection with Spring Framework 5.1 and Envers. The listener implementation does not require being annotated by @Service nor @Component because these classes are actually constructed by Hibernate and then the ManagedBeanRegistry is responsible for coordinating with whatever DI framework is being used to inject/wire dependencies.

    If your MyService is not being injected into the revision listener then there are few things to check:

    1. Verify that you're using Spring Framework 5.1 or later.
      While other spring components may use differing version schemes, its important to make sure that the underlying Spring Framework version is indeed 5.1+.
    2. Is the RevisionListener configured correctly?
      I typically configure this by adding a @RevisionEntity annotated class with the @Entity annotation and then specify the listener in the @RevisionEntity annotation's value attribute. If you'd rather, you should be able to use org.hibernate.envers.revision_listener to specifying the fully qualified class name of the listener class otherwise.
    3. Is your MyService injectable in any other spring bean?
      Perhaps the issue here is that Spring simply isn't constructing your MyService bean in the first place, which would lead to the dependency injection not supplying an implementation during bean injection of the revision listener.

    I have created a small demo here that you can reference. I will look into publishing a blog post about this on the Hibernate blog later in the month.


    UPDATE

    Just looking at the Spring Framework source briefly, I believe the issue is that you're manually creating a LocalContainerEntityManagerFactoryBean, which when the bean factory gets injected, does not actually create the necessary Hibernate configuration for dependency injection to happen.

    What I think you may need to do is manually set this yourself:

    @Bean
    @DependsOn({"dataSource"})
    public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(ConfigurableListableBeanFactory beanFactory) {
       // Create the LocalContainerEntityManagerBean like you were
    
       // Pass beanFactory here so right configuration gets applied
       em.setJpaProperties(additionalProperties(beanFactory));
       return em;
    }
    
    private Properties additionalProperties(ConfigurableListableBeanFactory beanFactory) {
      // add your properties here like you were before
      Properties properties = new Properties();
    
      // THIS HERE IS THE CRITICAL SETTING
      properties.put(
        "hibernate.resource.beans.container", 
         new SpringBeanContainer(beanFactory));
    
      return properties
    }
    

    While the LocalContainerEntityManagerFactoryBean is BeanFactoryAware, it simply sets an internal property and that's it. That property does not actually get set as the necessary configuration property for Hibernate to detect it, therefore Hibernate ends up defaulting to CDI injection since you're in a CDI environment.

    In the above, as the EntityManagerFactory bean is being constructed, we have the configuration pass in the BeanFactory instance. We pass that factory down into additionalProperties where we manually apply it to the right Hibernate configuration and that gets passed to the Hibernate bootstrap.