如何从 Spring Boot 应用程序获取 ServiceRegistry 实例?

How to get ServiceRegistry instance from Spring Boot application?

我正在尝试使用 CommandLineRunner 访问底层 Hibernate 数据库的信息,以便我最终可以转储模式文件。我需要访问服务注册表实例才能执行此操作。

我尝试查看是否可以通过以下代码从 AutoWired EntityManagerFactory 获取它:

package test;

import javax.persistence.EntityManagerFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.ServiceRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class AutoWiredTest implements CommandLineRunner {
  @Autowired
  private EntityManagerFactory emf;
  
  @Override
  public void run(String... args) 
  throws Exception {
    SessionFactoryImplementor sessionFactory = emf.unwrap(SessionFactoryImplementor.class);
    ServiceRegistry serviceRegistry = sessionFactory.getServiceRegistry();
    if( serviceRegistry == null )
      throw new Exception("Service registry is null");
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    metadataSources.buildMetadata();
 }

应用程序给我这个错误:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:761) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.4.jar:2.6.4]
        at test.AutoWiredTest.main(AutoWiredTest.java:31) ~[classes/:na]
Caused by: org.hibernate.HibernateException: Unexpected type of ServiceRegistry [org.hibernate.service.internal.SessionFactoryServiceRegistryImpl] encountered in attempt to build MetadataBuilder
        at org.hibernate.boot.internal.MetadataBuilderImpl.getStandardServiceRegistry(MetadataBuilderImpl.java:113) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at org.hibernate.boot.internal.MetadataBuilderImpl.<init>(MetadataBuilderImpl.java:93) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at org.hibernate.boot.MetadataSources.getMetadataBuilder(MetadataSources.java:146) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:202) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at test.AutoWiredTest.run(AutoWiredTest.java:26) ~[classes/:na]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:777) ~[spring-boot-2.6.4.jar:2.6.4]
        ... 5 common frames omitted

接下来,我尝试使用以下代码创建构建器:

package test;

import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BuilderTest implements CommandLineRunner {
  @Override
  public void run(String... args) 
  throws Exception {
    new StandardServiceRegistryBuilder().configure().build();
  }
  
  public static void main(String[] args) 
  throws Exception {
    SpringApplication.run(test.BuilderTest.class, args);
  }
}

导致此错误的原因:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:761) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.4.jar:2.6.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.4.jar:2.6.4]
        at test.BuilderTest.main(BuilderTest.java:18) ~[classes/:na]
Caused by: org.hibernate.internal.util.config.ConfigurationException: Could not locate cfg.xml resource [hibernate.cfg.xml]
        at org.hibernate.boot.cfgxml.internal.ConfigLoader.loadConfigXmlResource(ConfigLoader.java:53) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at org.hibernate.boot.registry.StandardServiceRegistryBuilder.configure(StandardServiceRegistryBuilder.java:254) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at org.hibernate.boot.registry.StandardServiceRegistryBuilder.configure(StandardServiceRegistryBuilder.java:243) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
        at test.BuilderTest.run(BuilderTest.java:13) ~[classes/:na]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:777) ~[spring-boot-2.6.4.jar:2.6.4]
        ... 5 common frames omitted

它正在寻找 cfg.xml 文件,但我已经在 application.properties 文件中定义了我的数据库配置:

hibernate.current_session_context_class=thread
hibernate.format_sql=false
hibernate.show_sql=false
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/marketing
spring.datasource.username=marketing
spring.datasource.password=[PASS]
spring.logging.level.root=ERROR
spring.logging.level.org.hibernate=INFO
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB103Dialect

知道出了什么问题吗?

似乎 Spring 只是没有将 Hibernate ServiceRegistry 注册为 bean。我查看了源代码,发现它的实例是在 SessionFactoryImpl 构造函数中创建的。

您可以尝试应用此 hack。

@SpringBootApplication
public class AutoWiredTest implements CommandLineRunner {
  @Autowired
  private EntityManagerFactory emf;
  
  @Override
  public void run(String... args) 
  throws Exception {
    SessionFactoryImplementor sessionFactory = emf.unwrap(SessionFactoryImplementor.class);
    ServiceRegistry serviceRegistry = sessionFactory.getServiceRegistry();
  }
  
  public static void main(String[] args) 
  throws Exception {
    SpringApplication.run(AutoWiredTest.class, args);
  }
}