如何将 spring beans 注入休眠环境 RevisionListener

How to inject spring beans into the hibernate envers RevisionListener

我正在使用 Spring 5.1 和 Hibernate 5.3.9,包括 hibernate-envers。 我不知道如何将 spring bean 注入休眠环境自定义 RevisionListener。

我试过了

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

  @Autowired
  private MyService myService;

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

当然 class 包含在 @ComponentScan 包解析中。 一个问题是 myService 没有注入到监听器中。

在 hibernate-envers 文档中:

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

从 Hibernate Envers 5.3 开始,RevisionListener 现在支持依赖注入。 此功能取决于各种依赖框架,例如 CDI 和 Spring,以在 Hibernate ORM bootstrap 期间提供必要的实现以支持注入。如果没有提供符合条件的实现,将在没有注入的情况下构造 RevisionListener。

不幸的是,我还没有找到任何有效的例子。

@纳罗斯 我在 spring 持久性 JPA 中设置了 EntityMaganerFactoryBean 配置 -> entityManager = 新 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 Server0\Tools\Binn\;C:\Program Files\Microsoft SQL Server0\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server0\DTS\Binn\;C:\Program Files\Microsoft SQL Server0\DTS\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC0\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC0\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server0\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}

在我的 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;
  }

使用 Spring Framework 5.1 和 Envers 设置 bean 注入确实没有太多涉及。侦听器实现不需要被 @Service@Component 注释,因为这些 classes 实际上是由 Hibernate 构造的,然后 ManagedBeanRegistry 负责与任何 DI 框架协调习惯于 inject/wire 依赖关系。

如果您的 MyService 没有被注入到修订侦听器中,那么需要检查的内容很少:

  1. 确认您使用的是 Spring Framework 5.1 或更高版本。
    虽然其他 spring 组件可能使用不同的版本方案,确保基础 Spring 框架版本确实是 5.1+ 很重要。
  2. RevisionListener 配置正确吗?
    我通常通过添加一个 @RevisionEntity 注释 class 和 @Entity 注释来配置它,然后指定@RevisionEntity 注释的值属性中的侦听器。如果您愿意,您应该能够使用 org.hibernate.envers.revision_listener 来指定监听器的完全限定 class 名称 class 否则。
  3. 你的 MyService 可以注入任何其他 spring bean 吗?
    也许这里的问题是 Spring 根本没有构建你的 MyService bean,这将导致依赖注入在修订侦听器的 bean 注入期间不提供实现。

我创建了一个小演示 here,您可以参考。我将考虑在本月晚些时候在 Hibernate 博客上发布一篇关于此的博客 post。


更新

简单地查看 Spring 框架源代码,我认为问题在于您手动创建了一个 LocalContainerEntityManagerFactoryBean,当注入 bean 工厂时,它实际上并没有创建必要的发生依赖注入的 Hibernate 配置。

我认为您可能需要自己手动设置:

@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
}

虽然 LocalContainerEntityManagerFactoryBeanBeanFactoryAware,但它只是设置了一个内部 属性 而已。 属性 实际上并没有设置为 Hibernate 检测它的必要配置 属性,因此 Hibernate 最终默认为 CDI 注入,因为您处于 CDI 环境中。

在上面,在构建 EntityManagerFactory bean 时,我们在 BeanFactory 实例中进行了配置传递。我们将该工厂向下传递到 additionalProperties,在那里我们手动将其应用到正确的 Hibernate 配置,然后传递到 Hibernate bootstrap。

如果使用Spring引导,最好使用ApplicationContextAware:

public class MyRevisionListener implements RevisionListener, ApplicationContextAware {

    private MyService myService;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.myService = applicationContext.getBean(MyService.class);
    }

    @Override
    public void newRevision(Object revisionEntity) {
        MyRevisionEntity revinfo = (MyRevisionEntity) revisionEntity;

        // Use myService here to set something on MyRevisionEntity
    }

}

这是使用 Spring Boot 2.3.9 测试的。

有其他方法...您可以使用 spring 中的静态 class...

检查....

import org.hibernate.envers.RevisionListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;


public class EnverRevisionListener implements RevisionListener{

    @Override
    public void newRevision(Object revisionEntity) {
        EnverRevisionEntity revEntity = (EnverRevisionEntity) revisionEntity;
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication auth = context.getAuthentication();
        revEntity.setUserName(auth.getName());
    }

}