Hibernate Schema Generator

Ich war auf der Suche nach einer Möglichkeit, automatisch mit Maven aus annotierten Hibernate Entitäten das entsprechende Datenbankschema zu generieren und in einer Datei abzuspeichern. Dabei bin ich auf der Seite iPROFS Technology Blog über den Hibernate Schema Generator gestolpert. Was mir aber für meinen Anwendungszweck fehlte war das Durchsuchen von bereits kompilierten Bibliotheken.

Daher habe ich zunächst die Klasse um die Methode getClassesFromJar ergänzt. Die Ausgaben nach System.out habe ich eingefügt, da ich die Klasse von Maven aus aufrufe und es als sinnvoll erachtete der Aktion ein wenig Gesprächigkeit zu geben.

private List getClassesFromJar(String packageName, URL resource)
        throws IOException, ClassNotFoundException {
    final List classes = new ArrayList<>();
    final String path = packageName.replace('.', '/');
    System.out.println("[INFO] Path: " + path);
    final JarFile jarFile = new JarFile(getJarFilename(resource));
    final Enumeration jarEntries = jarFile.entries();
    while (jarEntries.hasMoreElements()) {
        JarEntry jarEntry = jarEntries.nextElement();
        String name = jarEntry.getName();
        if (name.startsWith(path) && name.endsWith(".class")) {
            String className = name.substring(0, name.lastIndexOf('.')).replace('/', '.');
            System.out.println("[INFO] Class: " + className);
            classes.add(Class.forName(className));
        }
    }

    return classes;
}

Zusätzlich wurde die Methode getClasses um den entsprechenden Aufruf der Methode getClassesFromJar erweitert. Hier ist es wichtig, das Protokoll der URL des zu durchsuchenden Paketes auszuwerten.

public static final String PROTOCOL_JAR = "jar";

private List getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    try {
        final URL resource = getResource(packageName, getClassLoader());
        if (PROTOCOL_JAR.equals(resource.getProtocol())) {
            return getClassesFromJar(packageName, resource);
        } else {
            return getClasses(packageName, resource);
        }
    } catch (NullPointerException ex) {
        throw new ClassNotFoundException("'" + packageName +
                "' does not appear to be a valid package");
    }
}

Der Vollständigkeit halber hier nun die gesamte Klasse. Gegenüber dem Original habe ich zusätzlich noch einige strukturelle Anpassungen vorgenommen.

package de.volkerfaas.core.jpa;

import org.apache.commons.io.FileUtils;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class HibernateSchemaGenerator {

    public static final String PROTOCOL_JAR = "jar";

    public HibernateSchemaGenerator() {

    }

    /**
     * Main method to make this class executable.
     * @param args package name, path of output file, database dialect
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void main(String[] args)
            throws IOException, ClassNotFoundException {
        final String packageName = args[0];
        final String outputFilePath = args[1];
        final String databaseDialect = args[2];
        final HibernateSchemaGenerator schemaGenerator = new HibernateSchemaGenerator();
        schemaGenerator.generate(packageName, databaseDialect, outputFilePath);
    }

    /**
     * Generates a database in a file schema with the specified databaseDialect
     * from the entities in the given package.
     * @param packageName the package containing the entities
     * @param dialect the databaseDialect of the target database
     * @param outputFilePath the file path where the output is generated
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public void generate(String packageName, String dialect, String outputFilePath)
            throws IOException, ClassNotFoundException {
        String outputPath = outputFilePath.substring(0, outputFilePath.lastIndexOf('/'));
        FileUtils.forceMkdir(new File(outputPath));
        System.out.println("[INFO]");
        System.out.println("[INFO] --- hibernate-schema-generator ---");
        System.out.println("[INFO] Database Dialect: " + dialect);
        System.out.println("[INFO] Output file: " + outputFilePath);

        final Configuration configuration = new Configuration();
        configuration.setProperty("hibernate.hbm2ddl.auto", "create");
        configuration.setProperty("hibernate.dialect", dialect);
        getClasses(packageName).forEach(configuration::addAnnotatedClass);

        final SchemaExport schemaExport = new SchemaExport(configuration);
        schemaExport.setDelimiter(";");
        schemaExport.setOutputFile(outputFilePath);
        schemaExport.setFormat(false);
        schemaExport.execute(false, false, false, true);
    }

    private List getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        try {
            final URL resource = getResource(packageName, getClassLoader());
            if (PROTOCOL_JAR.equals(resource.getProtocol())) {
                return getClassesFromJar(packageName, resource);
            } else {
                return getClasses(packageName, resource);
            }
        } catch (NullPointerException ex) {
            throw new ClassNotFoundException("'" + packageName +
                    "' does not appear to be a valid package");
        }
    }

    private String getJarFilename(URL resource) throws UnsupportedEncodingException {
        final String filename = URLDecoder.decode(resource.getFile(), "UTF-8");

        return filename.substring(5, filename.indexOf("!"));
    }

    private ClassLoader getClassLoader() throws ClassNotFoundException {
        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader == null) {
            throw new ClassNotFoundException("Can't get class loader.");
        }

        return contextClassLoader;
    }

    private URL getResource(String packageName, ClassLoader classLoader)
            throws ClassNotFoundException {
        final String path = packageName.replace('.', '/');
        final URL resource = classLoader.getResource(path);
        if (resource == null) {
            throw new ClassNotFoundException("No resource for '" + path + "'");
        }

        return resource;
    }

    private List getClasses(String packageName, URL resource)
            throws ClassNotFoundException, UnsupportedEncodingException {
        final File directory = new File(URLDecoder.decode(resource.getFile(), "UTF-8"));
        final List classes = new ArrayList<>();
        if (directory.exists()) {
            final String[] files = directory.list();
            for (final String file : files) {
                if (file.endsWith(".class")) {
                    String className = packageName + '.' +
                            file.substring(0, file.length() - 6);
                    System.out.println("[INFO] Entity: " + className);
                    classes.add(Class.forName(className));
                }
            }
        } else {
            throw new ClassNotFoundException("'" + packageName + "' is not a valid package");
        }

        return classes;
    }

    private List getClassesFromJar(String packageName, URL resource)
            throws IOException, ClassNotFoundException {
        final List classes = new ArrayList<>();
        final String path = packageName.replace('.', '/');
        final JarFile jarFile = new JarFile(getJarFilename(resource));
        final Enumeration jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String name = jarEntry.getName();
            if (name.startsWith(path) && name.endsWith(".class")) {
                String className = name.substring(0, name.lastIndexOf('.')).replace('/', '.');
                System.out.println("[INFO] Entity: " + className);
                classes.add(Class.forName(className));
            }
        }

        return classes;
    }

}

Abschließend folgt der entsprechende Maven Aufruf des exec-maven-plugin im Build-Block der pom.xml.

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>1.3.2</version>
      <executions>
        <execution>
          <phase>test-compile</phase>
          <goals>
            <goal>java</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <mainClass>de.volkerfaas.core.jpa.HibernateSchemaGenerator</mainClass>
        <arguments>
          <argument>de.volkerfaas.emeraldedge.common.entity</argument>
          <argument>${project.basedir}/src/test/resources/sql/schema.sql</argument>
          <argument>org.hibernate.dialect.MySQL5Dialect</argument>
        </arguments>
      </configuration>
    </plugin>
  </plugins>
</build>

Schreibe einen Kommentar