Archive for March, 2008

A Gant Script to Call Hibernate’s SchemaExport

Monday, March 31st, 2008

Being new to Grails, I’ve been closely following the User and Dev mailing lists for tips and there was a recent discussion on the User list about accessing or generating the Hibernate schema script. The schema that Hibernate builds is good but typically DBAs need to customize things and the Hibernate output is a good starting point. It’s also useful to view the DDL between builds to monitor database changes.

In a traditional Spring/Hibernate app I use the schemaexport Ant task that comes with Hibernate Tools (this is a wrapper for SchemaExport), or by calling it directly if I need more control than the Ant task provides.

Running SchemaExport programmatically is pretty straightforward – you just have to build a Configuration from your domain classes (using hbm.xml files or annotated classes). But domain classes in Grails are (typically) .groovy files; I looked at the Grails/GORM code but couldn’t figure out how to properly build the Application, so the domain classes weren’t found and the Configuration was empty.

I’ve been reading the Grails source code trying to figure out how things work and I was looking at the code for the command-line scripts (e.g. “grails war”, “grails test-app”, etc.) and realized that I could borrow some setup code from there and get SchemaExport to work. It took a few hours to get going and a few more to distill it down to proper idiomatic Groovy (I am still a Java programmer …).

The code is below but you can download the script here. Some usage notes:

  • Usage: grails [environment] schema-export [generate | export] [stdout] [filename]
  • The action (generate or export), stdout option, and the filename are optional; if ommitted the action is generate, the filename defaults to ddl.sql in the project base dir, and ddl is only sent to stdout if the stdout option is present
  • Example 1: ‘grails schema-export’ generates ddl.sql for the default configuration
  • Example 2: ‘grails prod schema-export stdout’ generates ddl.sql for the ‘prod’ configuration, sending ddl to stdout in addition to the default file
  • Example 3: ‘grails dev schema-export c:/foo/bar/grails-app.sql’ generates the file ‘c:/foo/bar/grails-app.sql’ for the ‘dev’ configuration
  • Example 4: ‘grails prod schema-export export’ exports the schema (drop and create) using the ‘prod’ configuration
  • Example 5: ‘grails prod schema-export export stdout’ exports the schema using the ‘prod’ configuration, echoing the ddl to stdout
import org.hibernate.dialect.Dialect
import org.hibernate.dialect.DialectFactory
import org.hibernate.tool.hbm2ddl.SchemaExport
import org.codehaus.groovy.grails.orm.hibernate.cfg.DefaultGrailsDomainConfiguration
import org.springframework.jdbc.datasource.DriverManagerDataSource
import org.springframework.jdbc.support.JdbcUtils

grailsHome = Ant.project.properties.'environment.GRAILS_HOME'
includeTargets << new File("${grailsHome}/scripts/Bootstrap.groovy")

Properties props = new Properties()
String filename = "${basedir}/ddl.sql"
boolean export = false
boolean stdout = false

def configClasspath = {

  getClass().classLoader.rootLoader?.addURL(new File(classesDirPath).toURL())

  Ant.copy(todir: classesDirPath, file: "${basedir}/application.properties")
  Ant.copy(todir: classesDirPath, failonerror: false) {
    fileset(dir: "${basedir}/grails-app/conf",
           excludes: '*.groovy, log4j*, hibernate, spring')
    fileset(dir: "${basedir}/grails-app/conf/hibernate")
    fileset(dir: "${basedir}/src/java", excludes: '**/*.java')
  }
}

def configureFromArgs = {

  args = args ?: ''
  args.split('\n').each { arg ->
    arg = arg.trim()
    if (arg.length() > 0) {
      if (arg == 'export') {
        export = true
      }
      else if (arg == 'generate') {
        export = false
      }
      else if (arg == 'stdout') {
        stdout = true
      }
      else {
        // assume filename override
        filename = arg
      }
    }
  }
}

def populateProperties = {

  File dsFile = new File("${basedir}/grails-app/conf/DataSource.groovy")
  def dsConfig = null
  if (dsFile.exists()) {
    dsConfig = new ConfigSlurper(grailsEnv).parse(dsFile.text)
  }

  props.'hibernate.connection.username' = dsConfig?.dataSource?.username ?: 'sa'
  props.'hibernate.connection.password' = dsConfig?.dataSource?.password ?: ''
  props.'hibernate.connection.url' =
       dsConfig?.dataSource?.url ?: 'jdbc:hsqldb:mem:testDB'
  props.'hibernate.connection.driver_class' =
       dsConfig?.dataSource?.driverClassName ?: 'org.hsqldb.jdbcDriver'
  if (dsConfig?.dataSource?.dialect) {
    def dialect = dsConfig.dataSource.dialect
    if (dialect instanceof Class) {
      dialect = dialect.name
    }
    props.'hibernate.dialect' = dialect
  }
  else {
    println("WARNING: Autodetecting the Hibernate Dialect; "
    + "consider specifying the class name in DataSource.groovy")
    try {
      def ds = new DriverManagerDataSource(
          props.'hibernate.connection.driver_class',
          props.'hibernate.connection.url',
          props.'hibernate.connection.username',
          props.'hibernate.connection.password')
      String dbName = JdbcUtils.extractDatabaseMetaData(ds,
          'getDatabaseProductName')
      int majorVersion = JdbcUtils.extractDatabaseMetaData(ds,
          'getDatabaseMajorVersion')
      props.'hibernate.dialect' =
        DialectFactory.determineDialect(dbName, majorVersion).class.name
    }
    catch (Exception e) {
      println "ERROR: Problem autodetecting the Hibernate Dialect: ${e.message}"
      throw e
    }
  }
}

target('default': 'Run Hibernate SchemaExport') {
  depends(classpath, checkVersion, configureProxy, packageApp)

  configureFromArgs()

  File file = new File(filename)
  Ant.mkdir(dir: file.parentFile)

  configClasspath()
  loadApp()

  populateProperties()

  def configuration = new DefaultGrailsDomainConfiguration(
      grailsApplication: grailsApp,
      properties: props)

  def schemaExport = new SchemaExport(configuration)
    .setHaltOnError(true)
    .setOutputFile(file.path)
    .setDelimiter(';')

  String action = export ? "Exporting" : "Generating script to ${file.path}"
  println "${action} in environment '${grailsEnv}' using properties ${props}"

  if (export) {
    // 1st drop, warning exceptions
    schemaExport.execute(stdout, true, true, false)
    schemaExport.exceptions.clear()
    // then create
    schemaExport.execute(stdout, true, false, true)
  }
  else {
    // generate
    schemaExport.execute(stdout, false, false, false)
  }

  if (!schemaExport.exceptions.empty) {
    ((Exception)schemaExport.exceptions[0]).printStackTrace()
  }
}

Using Spring MVC Controllers in Grails

Thursday, March 27th, 2008

Update 03/22/2010: As of version 1.2 Grails has support for Spring MVC controllers; check out the 1.2 release notes

Groovy is slower than Java and sometimes dramatically slower. Realistically, this has little impact on a web application since response time is affected more by the database and network latency, so as long as the slowdown isn’t too dramatic, the benefits of Groovy and Grails far outweigh these concerns. And Grails is still way faster than Rails 🙂

But having said that, I was wondering how to use a regular Java Spring MVC controller and JSP instead of a Grails controller and a GSP (both of which use Groovy). Turns out it’s pretty easy:

  • Register the traditional Spring dispatcher servlet in web.xml (you’ll need to have run grails install-templates). In this example the name (SpringMVC) isn’t important, use whatever you want, and I’ve chosen to map *.action URLs to this controller and let Grails handle the rest:
<servlet>
   <servlet-name>SpringMVC</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>SpringMVC</servlet-name>
   <url-pattern>*.action</url-pattern>
</servlet-mapping>
  • Generate web-app/WEB-INF/SpringMVC-servlet.xml:
<?xml version='1.0' encoding='UTF-8'?>

<beans xmlns='http://www.springframework.org/schema/beans'
   xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
   xmlns:p='http://www.springframework.org/schema/p'
   xmlns:lang='http://www.springframework.org/schema/lang'
   xsi:schemaLocation='
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://www.springframework.org/schema/lang
      http://www.springframework.org/schema/lang/spring-lang-2.5.xsd'>

   <bean id='mvcHandlerMapping'
      class='org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
      p:order='1'>
      <property name='interceptors'>
         <list>
            <ref bean='openSessionInViewInterceptor' />
            <ref bean='localeChangeInterceptor' />
         </list>
      </property>
   </bean>

   <bean id='mvcViewResolver'
      class='org.springframework.web.servlet.view.UrlBasedViewResolver'
      p:viewClass='org.springframework.web.servlet.view.InternalResourceView'
      p:order='1'
      p:prefix='/WEB-INF/jsp/'
      p:suffix='.jsp'
   />

   <bean name='baseSimpleController' abstract='true' p:cacheSeconds='0'/>

   <bean name='jspController'
      class='com.foo.spring.controller.JspController'
      parent='baseSimpleController'
      abstract='true'
   />

   <!-- actions -->

   <bean name='/test.action'
      class='com.foo.spring.controller.TestController'
      parent='baseSimpleController'
      p:successView='test'
   />

   <bean name='/other.action' parent='jspController' p:successView='other' />

</beans>

And that’s it. Some notes:

  • the handler mapping uses the id mvcHandlerMapping since Grails will create one using the standard name of handlerMapping
  • since handler mappings are auto-discovered by default, you need to set the order attribute to something lower than the Grails mapping’s (which uses the default value of Integer.MAX_VALUE) so this mapping is accessed first
  • the HandlerInterceptors that are configured for the Grails mapping (OpenSessionInView, LocaleChange) won’t be automatically available to this mapping, but it’s simple to borrow them since they’re registered as beans; you can also add other custom interceptors to the list
  • I’ve created an optional abstract parent controller bean (baseSimpleController) for simple controllers (single-page, i.e. not form or wizard controllers)
  • I’ve also created a simple controller that just shows a JSP – this is useful for pages that don’t have any controller logic:
    package com.foo.spring.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.AbstractController;
    
    public class JspController extends AbstractController {
    
       private String _successView;
    
       @Override
       protected ModelAndView handleRequestInternal(
             final HttpServletRequest request,
             final HttpServletResponse response) {
    
          return new ModelAndView(_successView);
       }
    
       public void setSuccessView(final String view) {
          _successView = view;
       }
    }
    

I’ve mapped two sample URLs – /test.action, which uses a controller, and /other.action, which uses JspController to just show other.jsp.

Note that it is possible to use JSPs with Grails; Grails looks for a GSP using the specified name, but if it doesn’t find one it looks for a JSP (under /WEB-INF/grails-app/views/) and uses that if it exists. So another option is to use Grails controllers and JSP.

Big caveat: I haven’t used this in production yet – I’m just prototyping so I’ll have this available in the future just in case.

My First Grails App is Live

Monday, March 24th, 2008

Unfortunately it’s unlikely you’ll be able to see it, unless you’re a college student in need of help verifiying your enrollment status to get a deal on a flight at Student Universe.

The core app is pretty small – it’s a web-based IM client/live chat that allows a student to talk with a Students Only agent. The Grails part is there to persist everything to the database. However the actual app is fairly involved though, since I added an admin interface, lots of tests, and a bunch of other stuff in anticipation of re-using a lot of it in the new product that we’re working on (stealth mode for now …)

One of the things that attracted me to Students Only was their use of Groovy and Grails. I’ve played with Groovy off and on for years but never had a good reason to write a real app using it. But Grails is rapidly gaining momentum and it’s certainly been a blast working with it.

I’m still partially on the fence about Grails vs a traditional Spring/Hibernate app because of the performance overhead of Groovy’s dynamic dispatch. The stack traces are staggeringly large, and I’m not sure how much of a productivity boost I’m getting out of Groovy or Grails personally. The cool thing though is that Grails is flexible enough that you’re not locked into anything – you can use JPA-annotated entities and DAOs instead of GORM, JSP instead of GSP, etc. if you find that the “Grails Way” isn’t enough for your needs.

Of course the coolest thing about Grails is the plugins – there are a ton of them and more are announced all the time.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.