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