web-dev-qa-db-fra.com

Programmatique SchemaExport/SchemaUpdate avec Hibernate 5 et Spring 4

Avec Spring 4 et Hibernate 4, j'ai pu utiliser Reflection pour obtenir l'objet de configuration Hibernate à partir de l'environnement actuel, à l'aide du code suivant:

@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);

Avec Hibernate 5, je dois utiliser une MetadataImplementor, qui ne semble pas être disponible à partir de ces objets. J'ai également essayé d'utiliser MetadataSources avec le serviceRegistry. Mais il a été dit que ce n'est pas le bon type de ServiceRegistry.

Y a-t-il un autre moyen de faire fonctionner cela? 

21
Benjamin M

J'aimerais ajouter quelque chose à la réponse d'Aviad afin qu'elle soit complète à la demande de l'OP.

Les internes:

Pour obtenir une instance de MetadataImplementor, la solution de contournement consiste à enregistrer une instance de SessionFactoryBuilderFactory par le biais de la fonction ServiceLoader de Java. La méthode getSessionFactoryBuilder de ce service enregistré est ensuite appelée par MetadataImplementor avec une instance de ce dernier, lorsque hibernate est initialisé. Les références de code sont ci-dessous:

  1. Chargement du service
  2. Invocation de getSessionFactoryBuilder

Donc, pour obtenir finalement une instance de MetadataImplementor, vous devez implémenter SessionFactoryBuilderFactory et vous inscrire afin que ServiceLoader puisse reconnaître ce service:

Une implémentation 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;
    }
}

Pour enregistrer ce qui précède, créez un fichier texte simple dans le chemin suivant (en supposant qu'il s'agisse d'un projet maven, nous avons besoin en dernier lieu du dossier 'META-INF' dans le chemin d'accès aux classes):

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

Et le contenu du fichier texte ne doit comporter qu'une seule ligne (il peut même s'agir de plusieurs lignes si vous devez enregistrer plusieurs instances) indiquant le chemin de classe complet de votre implémentation de SessionFactoryBuilderFactory. Par exemple, pour la classe ci-dessus, si le nom de votre package est "com.votreentreprise.prj", le contenu du fichier doit être le suivant.

com.yourcompany.prj.MetadataProvider

Et c'est tout, si vous exécutez votre application, une application Spring ou une veille prolongée, vous aurez une instance de MetadataImplementor disponible via une méthode statique une fois que la veille prolongée est démarrée.

Mise à jour 1:

Il n'y a aucun moyen de l'injecter via Spring. J'ai fouillé dans le code source d'Hibernate et l'objet de métadonnées n'est stocké nulle part dans SessionFactory (c'est ce que nous obtenons de Spring). Donc, il n'est pas possible de l'injecter. Mais il y a deux options si vous le souhaitez à la manière du printemps:

  1. Étendez les classes existantes et personnalisez-les à partir de

LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder

LocalSessionFactoryBean est ce que vous configurez dans Spring et possède un objet MetadataSources. MetadataSources crée MetadataBuilder qui à son tour crée MetadataImplementor. Toutes les opérations ci-dessus ne stockent rien, elles créent simplement un objet à la volée et le retournent. Si vous souhaitez disposer d'une instance de MetaData, vous devez étendre et modifier les classes ci-dessus afin qu'elles stockent une copie locale des objets respectifs avant leur renvoi. De cette façon, vous pouvez avoir une référence à MetadataImplementor. Mais je ne recommanderais vraiment pas ceci sauf si c'est vraiment nécessaire, car les API pourraient changer avec le temps.

  1. D'autre part, si vous ne craignez pas de créer un MetaDataImplemetor à partir de SessionFactory, le code suivant vous aidera:

    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();
    }
    
7
James

L'idée de base pour ce problème est:

implémentation de org.hibernate.integrator.spi.Integrator qui stocke les données nécessaires à un détenteur. Enregistrez la mise en œuvre en tant que service et utilisez-la là où vous en avez besoin.

Exemple de travail que vous pouvez trouver ici https://github.com/valery-barysok/spring4-hibernate5-stackoverflow-34612019


créer la classe 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) {
    }
}

créer le fichier 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);
    }
}
9
valery.barysok

Jetez un coup d'oeil sur celui-ci:

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();
    }
}

Jetez un oeil sur Ce fil qui semble répondre à vos besoins

1
Aviad

Eh bien, je vais sur ceci:

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);
    }
}
0
K.Nicholas