Here we go. As it seems a lot of developers are migrating towards Hibernate annotations and EJB JPA, the following code assumes your classes are configured with annotations. This simple class takes the name of the package where you have your domain objects stored and generates ddl for mysql, oracle and hsql.
package com.jandrewthompson;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
/**
* @author john.thompson
*
*/
public class SchemaGenerator
{
private AnnotationConfiguration cfg;
public SchemaGenerator(String packageName) throws Exception
{
cfg = new AnnotationConfiguration();
cfg.setProperty("hibernate.hbm2ddl.auto","create");
for(Class<Object> clazz : getClasses(packageName))
{
cfg.addAnnotatedClass(clazz);
}
}
/**
* Method that actually creates the file.
* @param dbDialect to use
*/
private void generate(Dialect dialect)
{
cfg.setProperty("hibernate.dialect", dialect.getDialectClass());
SchemaExport export = new SchemaExport(cfg);
export.setDelimiter(";");
export.setOutputFile("ddl_" + dialect.name().toLowerCase() + ".sql");
export.execute(true, false, false, false);
}
/**
* @param args
*/
public static void main(String[] args) throws Exception
{
SchemaGenerator gen = new SchemaGenerator("org.jthompson.myapp.domain");
gen.generate(Dialect.MYSQL);
gen.generate(Dialect.ORACLE);
gen.generate(Dialect.HSQL);
}
/**
* Utility method used to fetch Class list based on a package name.
* @param packageName (should be the package containing your annotated beans.
*/
private ListgetClasses(String packageName) throws Exception
{
Listclasses = new ArrayList ();
File directory = null;
try
{
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
String path = packageName.replace('.', '/');
URL resource = cld.getResource(path);
if (resource == null) {
throw new ClassNotFoundException("No resource for " + path);
}
directory = new File(resource.getFile());
} catch (NullPointerException x) {
throw new ClassNotFoundException(packageName + " (" + directory
+ ") does not appear to be a valid package");
}
if (directory.exists()) {
String[] files = directory.list();
for (int i = 0; i < files.length; i++) {
if (files[i].endsWith(".class")) {
// removes the .class extension
classes.add(Class.forName(packageName + '.'
+ files[i].substring(0, files[i].length() - 6)));
}
}
} else {
throw new ClassNotFoundException(packageName
+ " is not a valid package");
}
return classes;
}
/**
* Holds the classnames of hibernate dialects for easy reference.
*/
private static enum Dialect
{
ORACLE("org.hibernate.dialect.Oracle10gDialect"),
MYSQL("org.hibernate.dialect.MySQLDialect"),
HSQL("org.hibernate.dialect.HSQLDialect");
private String dialectClass;
private Dialect(String dialectClass)
{
this.dialectClass = dialectClass;
}
public String getDialectClass()
{
return dialectClass;
}
}
}
It should be pretty easy to modify this code to suit your needs. If you are using hibernate xml configuration, just swap out the AnnotationConfiguration class for org.hibernate.cfg.Configuration.
Thanks, you saved my time.
ReplyDeleteHi Andrew, I find your code great, but i'm experience some issues width it. I call the generator new SchemaGenerator("entities") - thats my package - but it doesn't find the classes within the package or I get "ClassNotFoundException, entities it's not a valid package".
ReplyDeleteI don't have packages names like 'com.something.other', my package is a sub dir under src directory (src/entities) in my NetBeans project.
So, what I'm doing wrong?
Hi Gabriel, I know I'm way late chiming in here (I thought google was supposed to notify me when someone posts a comment?)... Did you put SchemaGenerator in the same project as your entities?
ReplyDeleteAssuming you did, it might be an issue with the way netbeans is setting up your classpath (I'm an Eclipse user, so I can't really help you here). Basically, the code is going to try to locate your 'entities' package using SchemaGenerator's classloader. So, if the classloader doesn't have visibility to 'entities', you will get that ClassNotFoundException.
Hope this helps shed some light. -- andrew
If you've got entities in a dependency, this alternative constructor lets you generate the DDL for a given class.
ReplyDeletepublic SchemaGenerator( Class classToGenerate ) throws Exception {
cfg = new AnnotationConfiguration();
cfg.setProperty( "hibernate.hbm2ddl.auto", "create" );
cfg.addAnnotatedClass( classToGenerate );
}
Why not to use SchemaExport class? See http://docs.jboss.org/hibernate/core/3.3/reference/en/html/toolsetguide.html
ReplyDeleteOndrej, my code IS using the SchemaExport class. As is usual with JBoss, things are not quite as simple as RedHat would like you to believe. First of all, the jboss documentation only mentions passing in a list of hibernate mapping files. It gives no examples of how to accomplish this task if your project uses annotations. To get any real mileage out of the SchemaExport class (don't get me wrong, I'm glad JBoss provides this tool) requires some supporting code to go along with it. I just wanted to show some folks how I utilize it in my projects.
ReplyDeleteCheers -- andrew
Thanks for sharing, saved a lot of time :)
ReplyDeleteThank you very much!
ReplyDeleteIf you are using eclipse on windows the default workspace location is in c:\Documents and Settings\workspace. The spaces in the path will require URL decoding of the getFile() like so:
directory = new File(URLDecoder.decode(resource.getFile(), "UTF-8"));
Also the lowercase C's in class caused me some grief before I realized the needed to be uppercase.
Good article and nicely explained. Thanks.
ReplyDeleteHi Andrew,
ReplyDeletereally thank you for this code ..for me its working like an normal application..but I am facing errors when I run this in JBOSS after including all hibernate dependencies also..and its throwing following error.
org.hibernate.AnnotationException: java.lang.NoSuchMethodException: org.hibernate.validator.ClassValidator
Can you help me to sort it out and thanks in Advance.
Regards,
Raja.
@raja
ReplyDeleteUsually, NoSuchMethodExceptions are thrown when you have conflicting versions of the same jar in your classpath. JBoss, if I'm not mistaken, ships with some hibernate jars. The jboss versions may have slightly different signatures than what you're expecting. Your options are to either attempt to upgrade the hibernate jars in Jboss, or to rework the code above to work with jboss's provided libs.
Hope this helps -- andrew
Hi Andrew,
ReplyDeletethanks for the answer..after upgrading the jars in JBOSS..its throwing the following error..java.lang.ClassCastException: org.hibernate.ejb.HibernatePersistence cannot be c
ast to javax.persistence.spi.PersistenceProvider
I am using annotations.jar and entitymanager.jar dependencies
can you please tell me how to remove these errors.
Hi,
ReplyDeleteactually how can I run this program in JBOSS?..is it possible to deploy this program in JBOSS..can you help me how can I make it.
Thanks in Advance,
Best Regards,
Raja.
@raja
ReplyDeleteI'm sure it could be run in Jboss, but honestly, that is not really the intent of the application. Ideally, generating the schema for your application would be done before you deploy to your application server.
I have changed the hibernate version in Jboss before, but it is not for the faint of heart. There are lots of dependencies that need to work together, and it is too much to try to help in this forum.
If you really want to use this in JBoss, I think it would be easier to port this code than to change Jboss.
Regards, and good luck! -- andrew
Hi Andrew,
ReplyDeletenice work. Helped us a lot until someone created a directory for test classes for some package to be scanned. We had:
src/main/java/de/foo/bar/beans/ and
src/test/java/de/foo/bar/beans/
Your getClasses only scans the latter directory. Therefore our annotated beans were not found anymore.
We fixed it with the following method:
private List> getClasses(String packageName) throws Exception
{
List classes = new ArrayList();
File directory = null;
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
String path = packageName.replace('.', '/');
Enumeration resources = cld.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
try {
directory = new File( resource.toURI().getPath() );
} catch (NullPointerException x) {
throw new ClassNotFoundException(packageName + " (" + directory
+ ") does not appear to be a valid package");
}
if (directory.exists()) {
String[] files = directory.list();
for (int i = 0; i < files.length; i++) {
if (files[i].endsWith(".class")) {
// removes the .class extension
classes.add(Class.forName(packageName + '.'
+ files[i].substring(0, files[i].length() - 6)));
}
}
}
}
return classes;
}
Here's a reworked function that works if your classes are contained in a .jar as well as simply in the filesystem
ReplyDeleteprivate List> getClasses(String packageName) throws Exception
{
final String classExt = ".class";
List files = FileUtils.listFilesInPackage(packageName);
List> classes = new ArrayList>(files.size());
for (String file : files) {
if (file.endsWith(classExt)) {
// removes the .class extension, and replaces '/' with '.'
String className = (file.substring(0, file.length() - classExt.length())).replace(File.separatorChar, '.');
classes.add(Class.forName(className));
}
}
return classes;
}
public static List listFilesInPackage(String packageName) throws ClassNotFoundException, IOException {
List classNames = new LinkedList();
File directory = null;
try
{
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
String path = packageName.replace('.', '/');
URL resource = cld.getResource(path);
if (resource == null) {
throw new ClassNotFoundException("No resource for " + path);
}
directory = new File(resource.getFile());
} catch (NullPointerException x) {
throw new ClassNotFoundException(packageName + " (" + directory
+ ") does not appear to be a valid package");
}
if (directory.exists()) { // Deal with file-system case
String[] files = directory.list();
for (int i = 0; i < files.length; i++) {
classNames.add(packageName.replace('.', File.separatorChar) + '.' + files[i]);
}
}
else { // Deal with case where files are within a JAR
final String[] parts = directory.getPath().split(".jar!\\\\");
if (parts.length == 2) {
String jarFilename = parts[0].substring(6) + ".jar";
String relativePath = parts[1].replace(File.separatorChar, '/');
JarFile jarFile = new JarFile(jarFilename);
final Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
final String entryName = entry.getName();
if ((entryName.length() > relativePath.length()) && entryName.startsWith(relativePath)) {
classNames.add(entryName.replace('/', File.separatorChar));
}
}
}
else {
throw new ClassNotFoundException(packageName
+ " is not a valid package");
}
}
return classNames;
}
For me, I made a change 'cause all my entities are into subpackages. So I recursively call the getClasses method ==>
ReplyDeleteif (directory.exists()) {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.isFile() && file.getName().endsWith(".class")) {
Class aClass = Class.forName(packageName + '.'
+ file.getName().substring(0, file.getName().length() - 6));
classes.add(aClass);
} else if (file.isDirectory()) {
getClasses(packageName + "." + file.getName());
}
}
} else {
throw new ClassNotFoundException(packageName
+ " is not a valid package");
}
What would be the change to use the persistence.xml to list the classes?
ReplyDeleteThanks!
Jeff
Jeff, I just created some code that does what you seem to need:
ReplyDeleteList classes() throws Exception {
File f = new File("src/test/resources/META-INF/persistence-test.xml");
String cname = "http://java.sun.com/xml/ns/persistence";
Namespace ns = Namespace.getNamespace(cname);
Document build = new org.jdom2.input.SAXBuilder().build(f);
Element pu = build.getRootElement().getChild("persistence-unit", ns);
List classes = new ArrayList<>();
for (Object clz : pu.getChildren("class", ns)) {
classes.add(Class.forName(((Element) clz).getText()));
}
return classes;
}
Crappy code and hardcoded reference to persistence file, but hey it works :)
Requires JDOM, tested with 2.0.2.
hi Andrew,
ReplyDeleteI am using entities as dependency in pom.xml as :
com.org
model
1.2
All entities come in com.org.entity package under src/main/java folder in the library whose dependency I am using to get entities. It is compiled. no error in compilation.
I am giving com.org.entity package name to SchemaGenerator but I am getting Exception as :
file:/Users/user1/.m2/repository/com/org/entity/model/1.2/model-1.2.jar!/com/org
Exception in thread "main" java.lang.ClassNotFoundException: com.org.entity is not a valid package.
Can you suggest me what I am doing wrong.
Thanks
Thanks for this. Works great!
ReplyDelete