java - SchemaExport/SchemaUpdate programático con Hibernate 5 y Spring 4
spring-4 hibernate-5.x (4)
Bueno, vaya a esto:
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);
}
}
Con Spring 4 y Hibernate 4, pude usar Reflection para obtener el objeto de configuración de Hibernate del entorno actual, usando este código:
@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);
Con Hibernate 5, debo usar algo de MetadataImplementor
, que no parece estar disponible en ninguno de esos objetos. También intenté usar MetadataSources
con serviceRegistry
. Pero sí decía que es el tipo incorrecto de ServiceRegistry
.
¿Hay alguna otra manera de hacer que esto funcione?
Echa un vistazo a este:
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();
}
}
Echa un vistazo a este hilo que parece responder a tus necesidades
La idea básica para este problema es:
implementación de org.hibernate.integrator.spi.Integrator
que almacena los datos requeridos para algún titular. Registre la implementación como un servicio y utilícelo donde lo necesite.
Ejemplo de trabajo que puede encontrar aquí https://github.com/valery-barysok/spring4-hibernate5--34612019
crear la clase 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) {
}
}
crear el archivo 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);
}
}
Me gustaría sumarme a la respuesta de Aviad para completarla de acuerdo con la solicitud de OP.
Los internos:
Para obtener una instancia de MetadataImplementor, la solución es registrar una instancia de SessionFactoryBuilderFactory través de la función ServiceLoader de Java. El método getSessionFactoryBuilder este servicio registrado luego es invocado por MetadataImplementor con una instancia de sí mismo, cuando hibernate es bootstrapped. Las referencias del código están abajo:
Entonces, para obtener finalmente una instancia de MetadataImplementor, debe implementar SessionFactoryBuilderFactory y registrarse para que ServiceLoader pueda reconocer este servicio:
Una implementación de 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;
}
}
Para registrar lo anterior, cree un archivo de texto simple en la siguiente ruta (asumiendo que es un proyecto experto, en última instancia necesitamos que la carpeta ''META-INF'' esté disponible en el classpath):
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
Y el contenido del archivo de texto debe ser una sola línea (incluso puede ser varias líneas si necesita registrar varias instancias) que indique la ruta de clase completa de su implementación de SessionFactoryBuilderFactory. Por ejemplo, para la clase anterior, si el nombre de su paquete es ''com.yourcompany.prj'', el siguiente debe ser el contenido del archivo.
com.yourcompany.prj.MetadataProvider
Y eso es todo, si ejecuta su aplicación, aplicación de Spring o hibernación independiente, tendrá una instancia de MetadataImplemento disponible a través de un método estático una vez que la hibernación se reinicie.
Actualización 1:
No hay forma de que se pueda inyectar a través de Spring. Busqué en el código fuente de Hibernate y el objeto de metadatos no se almacena en ningún lugar en SessionFactory (que es lo que obtenemos de Spring). Por lo tanto, no es posible inyectarlo. Pero hay dos opciones si lo quieres a la manera de Spring:
- Amplíe las clases existentes y personalice todo el camino desde
LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder
LocalSessionFactoryBean es lo que configura en Spring y tiene un objeto de MetadataSources. MetadataSources crea MetadataBuilder que a su vez crea MetadataImplementor. Todas las operaciones anteriores no almacenan nada, solo crean objetos sobre la marcha y regresan. Si desea tener una instancia de MetaData, debe extender y modificar las clases anteriores para que almacenen una copia local de los objetos respectivos antes de que regresen. De esa forma puede tener una referencia a MetadataImplementor. Pero realmente no recomendaría esto a menos que sea realmente necesario, porque las API podrían cambiar con el tiempo.
Por otro lado, si no te importa construir un MetaDataImplemetor desde SessionFactory, el siguiente código te ayudará:
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(); }