使用 Hibernate 5 和 Spring 4 的程序化 SchemaExport / SchemaUpdate
Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4
使用 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
,这些对象似乎都不可用。我还尝试将 MetadataSources
与 serviceRegistry
一起使用。但它确实说这是错误的 ServiceRegistry
.
有没有其他方法可以让它工作?
看看这个:
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();
}
}
看看 似乎可以满足您的需求
我想根据 OP 的要求补充 Aviad 的答案,使其完整。
内部结构:
为了获得 MetadataImplementor 的一个实例,解决方法是在引导休眠时用它自己的一个实例注册一个 SessionFactoryBuilderFactory through Java's ServiceLoader facility. This registered service's getSessionFactoryBuilder method is then invoked by MetadataImplementor 的实例。代码参考如下:
因此,最终要获得 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' 文件夹在 class 路径中可用):
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
并且文本文件的内容应该是单行(如果您需要注册多个实例,甚至可以是多行)说明您实现 SessionFactoryBuilderFactory 的完全限定 class 路径。比如上面的class,如果你的包名是'com.yourcompany.prj',下面应该是文件内容
com.yourcompany.prj.MetadataProvider
就是这样,如果您 运行 您的应用程序,spring 应用程序或独立的休眠,一旦休眠被引导,您将通过静态方法获得一个 MetadataImplementor 实例。
更新 1:
无法通过 Spring 注入。我深入研究了 Hibernate 的源代码,发现元数据对象没有存储在 SessionFactory 中的任何地方(这是我们从 Spring 获得的)。所以,不可能注入它。但是如果你想要 Spring 的方式,有两种选择:
- 扩展现有 class 并自定义
LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder
LocalSessionFactoryBean 是您在Spring 中配置的,它有一个 MetadataSources 对象。 MetadataSources 创建 MetadataBuilder,后者又创建 MetadataImplementor。以上所有操作都不存储任何内容,它们只是动态创建对象 return。如果你想要一个 MetaData 的实例,你应该扩展和修改上面的 classes 以便它们在 return 之前存储各自对象的本地副本。这样您就可以引用 MetadataImplementor。但除非真的需要,否则我不会真的推荐这个,因为 API 可能会随着时间而改变。
另一方面,如果您不介意从 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();
}
这个问题的基本思路是:
org.hibernate.integrator.spi.Integrator
的实施,将所需的数据存储到某些持有者。将实现注册为服务并在需要的地方使用它。
您可以在此处找到工作示例 https://github.com/valery-barysok/spring4-hibernate5-Whosebug-34612019
创建org.hibernate.integrator.api.integrator.Integrator
class
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);
}
}
好吧,我继续:
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);
}
}
使用 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
,这些对象似乎都不可用。我还尝试将 MetadataSources
与 serviceRegistry
一起使用。但它确实说这是错误的 ServiceRegistry
.
有没有其他方法可以让它工作?
看看这个:
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();
}
}
看看
我想根据 OP 的要求补充 Aviad 的答案,使其完整。
内部结构:
为了获得 MetadataImplementor 的一个实例,解决方法是在引导休眠时用它自己的一个实例注册一个 SessionFactoryBuilderFactory through Java's ServiceLoader facility. This registered service's getSessionFactoryBuilder method is then invoked by MetadataImplementor 的实例。代码参考如下:
因此,最终要获得 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' 文件夹在 class 路径中可用):
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
并且文本文件的内容应该是单行(如果您需要注册多个实例,甚至可以是多行)说明您实现 SessionFactoryBuilderFactory 的完全限定 class 路径。比如上面的class,如果你的包名是'com.yourcompany.prj',下面应该是文件内容
com.yourcompany.prj.MetadataProvider
就是这样,如果您 运行 您的应用程序,spring 应用程序或独立的休眠,一旦休眠被引导,您将通过静态方法获得一个 MetadataImplementor 实例。
更新 1:
无法通过 Spring 注入。我深入研究了 Hibernate 的源代码,发现元数据对象没有存储在 SessionFactory 中的任何地方(这是我们从 Spring 获得的)。所以,不可能注入它。但是如果你想要 Spring 的方式,有两种选择:
- 扩展现有 class 并自定义
LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder
LocalSessionFactoryBean 是您在Spring 中配置的,它有一个 MetadataSources 对象。 MetadataSources 创建 MetadataBuilder,后者又创建 MetadataImplementor。以上所有操作都不存储任何内容,它们只是动态创建对象 return。如果你想要一个 MetaData 的实例,你应该扩展和修改上面的 classes 以便它们在 return 之前存储各自对象的本地副本。这样您就可以引用 MetadataImplementor。但除非真的需要,否则我不会真的推荐这个,因为 API 可能会随着时间而改变。
另一方面,如果您不介意从 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(); }
这个问题的基本思路是:
org.hibernate.integrator.spi.Integrator
的实施,将所需的数据存储到某些持有者。将实现注册为服务并在需要的地方使用它。
您可以在此处找到工作示例 https://github.com/valery-barysok/spring4-hibernate5-Whosebug-34612019
创建org.hibernate.integrator.api.integrator.Integrator
class
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);
}
}
好吧,我继续:
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);
}
}