Schema export with Hibernate 4 and Maven

I recently needed to upgrade a project from Hibernate 3.6.9 to 4.1.3. Upgrading the code went fairly smoothly. However, upgrading the build turned out to be more difficult. The reason was the build was using hibernate3-maven-plugin to create an SQL schema file. The plugin, as its name suggests, supported only Hibernate 3. The main issue was that the plugin's code tried to use Hibernate classes which were relocated to different packages in the 4.x version. I tried updating the plugin from version 2.2 to 3.0, but it didn't work.

 

So I decided to do it myself. The schema export is done by a hibernate class called SchemaExport. So the best approach I found was to use gmaven to call the class. Here is the result, for anyone else encountering the same issue.

 

What I needed to do is:

  • Find all persistent classes in the build - for this I used the scannotation project.
  • Get class instances - I created a ClassLoader instance with the build compile path and used a hibernate utility
  • Configure an instance of SchemaExport and execute it
  • Print any errors

 

Here is the groovy code:

import org.scannotation.AnnotationDB
import org.scannotation.archiveiterator.FileIterator
import java.io.File
import javax.persistence.Entity
import javax.persistence.MappedSuperclass
import javax.persistence.Embeddable
import org.hibernate.cfg.Configuration
import org.hibernate.tool.hbm2ddl.SchemaExport
import org.hibernate.internal.util.ReflectHelper
 
def classLoader() {
    def classpathElements = project.compileClasspathElements + project.build.outputDirectory + project.build.testOutputDirectory
    URL[] urls = classpathElements.collect{new File(it).toURL()}.toArray()
    new URLClassLoader( urls, this.class.classLoader )
}

def scan(String... directories) {
    def annotationDb = new AnnotationDB()
    for (dir in directories) {
        log.info("Scanning ${dir}")
        annotationDb.scanArchives(new File(dir).toURL())
    }
    def index = annotationDb.annotationIndex
    def entityClasses = index.get(Entity.class.name) + index.get(MappedSuperclass.class.name) + index.get(Embeddable.class.name)
    return entityClasses
}

def export(classes) {
    Configuration config = new Configuration();
    Properties properties = new Properties();
    log.info("properties: ${project.properties}")
    def propertyfile = new FileInputStream(new File(project.basedir, project.properties.propertyfile))
    properties.load(propertyfile)
    propertyfile.close()

    config.setProperties(properties);
    for (c in classes) config.addAnnotatedClass(c)
    SchemaExport export = new SchemaExport(config);

    File file = new File(project.properties.outputfile)
    if (!file.absolute) {
        file = new File(project.build.outputDirectory, file)
    }
    if (!file.exists()) {
        file.parentFile.mkdirs();
    } else if (!file.isFile()) {
        fail("${file} is not a file")
    }

    log.info("exporting to ${file}")
    export.setOutputFile(file.absolutePath)
    export.setDelimiter(project.properties.delimiter ?: ";")
    export.setFormat(project.properties.format?.toBoolean() ?: false)

    export.execute(true, false, false, true)

    export.exceptions.each{log.error(it.toString())}
}

// The following line is a workaround to a bug in gmaven when trying to access project.properties inside a method fails
project.properties

def classNames = scan(project.build.outputDirectory, project.build.testOutputDirectory)
def oldClassLoader = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = classLoader()
def classes = classNames.collect {ReflectHelper.classForName(it, this.class);}
export(classes)
Thread.currentThread().contextClassLoader = oldClassLoader

 

Note the use of project.properties to get arguments passed to the script.

 

In the pom.xml file I added:

<plugin>
  <groupId>org.codehaus.gmaven</groupId>
  <artifactId>gmaven-plugin</artifactId>
  <executions>
    <execution>
      <phase>process-classes</phase>
      <goals>
        <goal>execute</goal>
      </goals>
      <configuration>
        <classpath>
           <element>
             <groupId>org.scannotation</groupId>
             <artifactId>scannotation</artifactId>
             <version>1.0.3</version>
           </element>
        </classpath>
        <properties>
          <propertyfile>${pom.basedir}/src/main/resources/prod-db.properties</propertyfile>
        </properties>
        <source>${pom.basedir}/schema_export.groovy</source>
      </configuration>
    </execution>
  </executions>
</plugin>

 

That's it. HTH

Developer