【问题标题】:Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4使用 Hibernate 5 和 Spring 4 的程序化 SchemaExport / SchemaUpdate
【发布时间】:2016-04-09 07:08:25
【问题描述】:

使用 Spring 4 和 Hibernate 4,我能够使用反射从当前环境中获取 Hibernate 配置对象,使用以下代码:

@Autowired LocalContainerEntityManagerFactoryBean lcemfb;

EntityManagerFactoryImpl emf = (EntityManagerFactoryImpl) lcemfb.getNativeEntityManagerFactory();
SessionFactoryImpl sf = emf.getSessionFactory();
SessionFactoryServiceRegistryImpl serviceRegistry = (SessionFactoryServiceRegistryImpl) sf.getServiceRegistry();
Configuration cfg = null;

try {
    Field field = SessionFactoryServiceRegistryImpl.class.getDeclaredField("configuration");
    field.setAccessible(true);
    cfg = (Configuration) field.get(serviceRegistry);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
}

SchemaUpdate update = new SchemaUpdate(serviceRegistry, cfg);

对于 Hibernate 5,我必须使用一些 MetadataImplementor,这似乎不适用于任何这些对象。我还尝试将MetadataSourcesserviceRegistry 一起使用。但它确实说这是错误的ServiceRegistry

还有其他方法可以让这个工作吗?

【问题讨论】:

标签: java spring hibernate spring-4 hibernate-5.x


【解决方案1】:

这个问题的基本思路是:

org.hibernate.integrator.spi.Integrator 的实现,它将所需的数据存储到某个持有者。将实现注册为服务并在需要的地方使用它。

你可以在这里找到工作示例https://github.com/valery-barysok/spring4-hibernate5-stackoverflow-34612019


创建org.hibernate.integrator.api.integrator.Integrator

import hello.HibernateInfoHolder;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;

public class Integrator implements org.hibernate.integrator.spi.Integrator {

    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
        HibernateInfoHolder.setMetadata(metadata);
        HibernateInfoHolder.setSessionFactory(sessionFactory);
        HibernateInfoHolder.setServiceRegistry(serviceRegistry);
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    }
}

创建META-INF/services/org.hibernate.integrator.spi.Integrator 文件

org.hibernate.integrator.api.integrator.Integrator

import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        new SchemaExport((MetadataImplementor) HibernateInfoHolder.getMetadata()).create(true, true);
        new SchemaUpdate(HibernateInfoHolder.getServiceRegistry(), (MetadataImplementor) HibernateInfoHolder.getMetadata()).execute(true, true);
    }
}

【讨论】:

【解决方案2】:

我想根据 OP 的要求补充 Aviad 的答案,以使其完整。

内部结构:

为了获得 MetadataImplementor 的实例,解决方法是通过 Java 的 ServiceLoader 工具注册 SessionFactoryBuilderFactory 的实例。这个注册服务的getSessionFactoryBuilder 方法然后由MetadataImplementor 调用,并在hibernate 被引导时使用它自己的一个实例。代码参考如下:

  1. Service Loading
  2. Invocation of getSessionFactoryBuilder

因此,最终要获得 MetadataImplementor 的实例,您必须实现 SessionFactoryBuilderFactory 并注册,以便 ServiceLoader 可以识别此服务:

SessionFactoryBuilderFactory 的实现:

public class MetadataProvider implements SessionFactoryBuilderFactory {

    private static MetadataImplementor metadata;

    @Override
    public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {
        this.metadata = metadata;
        return defaultBuilder; //Just return the one provided in the argument itself. All we care about is the metadata :)
    }

    public static MetadataImplementor getMetadata() {
        return metadata;
    }
}

为了注册上述内容,在以下路径中创建简单的文本文件(假设它是一个maven项目,最终我们需要在类路径中提供'META-INF'文件夹):

src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory

文本文件的内容应该是单行(如果您需要注册多个实例,甚至可以是多行),说明您的 SessionFactoryBuilderFactory 实现的完全限定类路径。比如上面这个类,如果你的包名是'com.yourcompany.prj',那么文件的内容应该如下。

com.yourcompany.prj.MetadataProvider

就是这样,如果您运行您的应用程序、spring 应用程序或独立的 hibernate,一旦 hibernate 被引导,您将通过静态方法获得一个 MetadataImplementor 实例。

更新 1:

它无法通过 Spring 注入。我深入研究了 Hibernate 的源代码,元数据对象没有存储在 SessionFactory 的任何地方(这是我们从 Spring 中得到的)。因此,无法注入它。但是如果你想用 Spring 的方式有两种选择:

  1. 扩展现有类并自定义

LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder

LocalSessionFactoryBean 是你在 Spring 中配置的,它有一个 MetadataSources 对象。 MetadataSources 创建 MetadataBuilder,后者又创建 MetadataImplementor。以上所有操作都不存储任何内容,它们只是动态创建对象并返回。如果你想要一个 MetaData 的实例,你应该扩展和修改上面的类,以便它们在返回之前存储相应对象的本地副本。这样您就可以参考 MetadataImplementor。但除非真的需要,否则我不会真的推荐这个,因为 API 可能会随着时间而改变。

  1. 另一方面,如果您不介意从 SessionFactory 构建 MetaDataImplemetor,以下代码将帮助您:

    EntityManagerFactoryImpl emf=(EntityManagerFactoryImpl)lcemfb.getNativeEntityManagerFactory();
    SessionFactoryImpl sf=emf.getSessionFactory();
    StandardServiceRegistry serviceRegistry = sf.getSessionFactoryOptions().getServiceRegistry();
    MetadataSources metadataSources = new MetadataSources(new BootstrapServiceRegistryBuilder().build());
    Metadata metadata = metadataSources.buildMetadata(serviceRegistry);
    SchemaUpdate update=new SchemaUpdate(serviceRegistry,metadata); //To create SchemaUpdate
    
    // You can either create SchemaExport from the above details, or you can get the existing one as follows:
    try {
        Field field = SessionFactoryImpl.class.getDeclaredField("schemaExport");
        field.setAccessible(true);
        SchemaExport schemaExport = (SchemaExport) field.get(serviceRegistry);
    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
        e.printStackTrace();
    }
    

【讨论】:

  • 没有其他方法可以通过 Spring 注入吗?我不喜欢那些文本文件,我必须在重命名或重构时跟踪它们。
  • 我刚刚用一种在 Spring 中使用它的方式更新了我的答案,就像您之前在 Hibernate 4 中使用它的方式一样
  • "new SchemaUpdate(serviceRegistry,metadata)" 使用 Hibernate 5.2 给我一个未定义此构造函数的错误。这个现在搬到别的地方了吗?
  • @PeriataBreatta,是的,现在只有一个无参数构造函数:docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/tool/… 它更易于使用:blog.exxeta.com/2016/10/14/…
  • 这个例子通常是有效的。只想提一下 LocalSessionFactoryBean 有方法 setHibernateIntegrators ,您可以在其中设置积分器而无需创建文本文件并将元数据传递给 SchemaExport。它对我有用。
【解决方案3】:

看看这个:

public class EntityMetaData implements SessionFactoryBuilderFactory {

    private static final ThreadLocal<MetadataImplementor> meta = new ThreadLocal<>();

    @Override
    public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {
        meta.set(metadata);
        return defaultBuilder;
    }

    public static MetadataImplementor getMeta() {
        return meta.get();
    }
}

看看This Thread,它似乎可以满足您的需求

【讨论】:

  • 谢谢。我稍后会试试这个。并报告回来。
  • 如何与 Spring 集成?该类不会被调用,因此 getMeta() 返回 null。
  • 嗯,这需要更改 METADATA 文件夹。你为什么需要这个?目的是什么?
  • Yes..getMeta() 返回 null 因为 getSessionFactoryBuilder 方法没有被调用。
【解决方案4】:

好吧,我继续说:

public class SchemaTranslator {
    public static void main(String[] args) throws Exception {
        new SchemaTranslator().run();
    }
    private void run() throws Exception {    
        String packageName[] = { "model"};    
        generate(packageName);
    }   
    private List<Class<?>> getClasses(String packageName) throws Exception {
        File directory = null;
        try {
            ClassLoader cld = getClassLoader();
            URL resource = getResource(packageName, cld);
            directory = new File(resource.getFile());
        } catch (NullPointerException ex) {
            throw new ClassNotFoundException(packageName + " (" + directory + ") does not appear to be a valid package");
        }
        return collectClasses(packageName, directory);
    }
    private ClassLoader getClassLoader() throws ClassNotFoundException {
        ClassLoader cld = Thread.currentThread().getContextClassLoader();
        if (cld == null) {
            throw new ClassNotFoundException("Can't get class loader.");
        }
        return cld;
    }
    private URL getResource(String packageName, ClassLoader cld) throws ClassNotFoundException {
        String path = packageName.replace('.', '/');
        URL resource = cld.getResource(path);
        if (resource == null) {
            throw new ClassNotFoundException("No resource for " + path);
        }
        return resource;
    }
    private List<Class<?>> collectClasses(String packageName, File directory) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        if (directory.exists()) {
            String[] files = directory.list();
            for (String file : files) {
                if (file.endsWith(".class")) {
                    // removes the .class extension
                    classes.add(Class.forName(packageName + '.' + file.substring(0, file.length() - 6)));
                }
            }
        } else {
            throw new ClassNotFoundException(packageName + " is not a valid package");
        }
        return classes;
    }
    private void generate(String[] packagesName) throws Exception {
        Map<String, String> settings = new HashMap<String, String>();
        settings.put("hibernate.hbm2ddl.auto", "drop-create");
        settings.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL94Dialect");
        MetadataSources metadata = new MetadataSources(
                new StandardServiceRegistryBuilder()
                        .applySettings(settings)
                        .build());    
        for (String packageName : packagesName) {
            System.out.println("packageName: " + packageName);
            for (Class<?> clazz : getClasses(packageName)) {
                System.out.println("Class: " + clazz);
                metadata.addAnnotatedClass(clazz);
            }
        }
        SchemaExport export = new SchemaExport(
                (MetadataImplementor) metadata.buildMetadata()
        );
        export.setDelimiter(";");
        export.setOutputFile("db-schema.sql");
        export.setFormat(true);
        export.execute(true, false, false, false);
    }
}

【讨论】:

    猜你喜欢
    • 2015-11-12
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    • 2020-05-26
    • 2016-01-29
    • 2016-07-14
    • 2016-02-29
    相关资源
    最近更新 更多