ActiveJDBC Presentation At Austin Groovy and Grails Group

I gave a presentation on ActiveJDBC last week at the Austin Groovy and Grails Group (@AustinGGUG on Twitter).

I gave two presentations before the main one.

I talked about the validation annotations I made for POGOs.

I talked about making a Gradle app that you can run from the Groovy shell, since that was how I was going to interface with ActiveJDBC.

The “main event” was the ActiveJDBC talk. Many of the people there knew about ORMs, either through Hibernate or GORM. I spent some time basically walking though some of the pages on the ActiveJDBC website to show the capabilities of ActiveJDBC. Some of the hightlights were the Getting Started page,  getters and settersrelationships, transactions, validators, optimistic locking, and some of the ways ActiveJDBC adheres to the ActiveRecord pattern, and the ways you can override the defaults.

All you need to do to get started is to extend the ActiveJDBC Model class, and you are good to go:

public class Person extends Model {}

This magic works because of instrumentation, which honestly I had not heard of before I tried to get it to work with Groovy. My understanding is that instrumentation adds byte code to a Java class after it has been compiled. I think it is mostly used by profiling tools.

ActiveJDBC gives directions on how to do this with Ant and Maven. I had to do some digging to get it to work with Gradle. I was able to do it with help from some code used by the Griffon ActiveJDBC plugin and some code in Griffon itself.

I added a method called addURLIfNotPresent, which adds files to the classpath of a classloader:

def addUrlIfNotPresent( to, what ) {
    
    if ( !to || !what ) { return }
    def urls = to.URLs.toList()
    switch( what.class ) {
      case URL: what = new File( what.toURI() ); break
      case String: what = new File( what ); break
      case GString: what = new File( what.toString() ); break
      case File: break; // ok
      default:
        println "Don't know how to deal with $what as it is not an URL nor a File"
        System.exit( 1 )
    }

    if ( what.directory && !what.exists() ) { what.mkdirs() }
    def url = what.toURI().toURL()
    if ( !urls.contains( url ) && ( what.directory || !urls.find{ it.path.endsWith( what.name ) } ) ) {
        to.addURL( url )
    }
}

This is called at the end of the compileGroovy step, thanks to the doLast() method:

compileGroovy.doLast {
    println( "In compileGroovy.doLast" )
    Instrumentation instrumentation = new Instrumentation()
    projectMainClassesDir = sourceSets.main.output.classesDir.getPath()
    rootLoader = this.class.classLoader.rootLoader
    instrumentation.outputDirectory = projectMainClassesDir
    addUrlIfNotPresent rootLoader, projectMainClassesDir
    addUrlIfNotPresent Instrumentation.class.classLoader, projectMainClassesDir
    instrumentation.instrument()
    activejdbcInstrumentSpyFile = new File( ".activejdbc_instrument" )
    activejdbcInstrumentSpyFile.text = new Date().toString()
}

I made a Grails app that had a few tables, and I had a Gradle app that used ActiveJDBC that pointed to the same database. I would make changes in one, and view them in the other, and go back and forth.

I also covered using Groovy metaprogramming to make ActiveJDBC more idiomatic in Groovy. ActiveJDBC does not provide getters and setters out of the box.

Here is how you would get the “name” field from a record in the People table:

Person p = Person.findById();
name = p.get("name");

Here is how you would set the name field:

Person p = new Person();
p.set("name", "John");
p.save();

The docs state you could write your own setter like this:

public class Person extends Model{
   public void setFirstName(String firstName){
      set("first_name", firstName);
   }
}

I showed how to use Groovy metaprogramming to do this automatically. I wrote something that will look at a model class, get a list of its fields, make getters and setters, and also make extra fields for particular data types (findBy$FIELD for varchar, findWhere$FIELDGreaterThan, findWhere$FIELDLessThan, findWhere$FIELDBetween for numbers). There are a lot of print statements for the presentation audience.

package info.shelfunit.active.share

import org.javalite.activejdbc.Model
import org.javalite.activejdbc.ColumnMetadata

class MetaLoader {
    
    static void createNewMethods() {
        def classList = [ Book.class, BookAuthor.class, SingleState.class ]
        def theTokens = []
        def capTokens = []
        
        classList.each { nextClass ->
            
            println "-- Working with ${nextClass.getCanonicalName()}"
            def columnMap = nextClass.getMetaModel().getColumnMetadata()
        
            nextClass.getMetaModel().getAttributeNamesSkipGenerated().each { theProp ->
                println "next prop is ${theProp}"
                theTokens.clear()
                capTokens.clear()
                theTokens = theProp.tokenize( '_' )
                theTokens.each { capTokens << it.capitalize() }
                
                println "Trying to make ${'get' + capTokens.join()}"
                
                nextClass.metaClass."${'get' + capTokens.join()}" { ->
                    delegate.get( theProp )
                }
                println "Making setter"
                nextClass.metaClass."${'set' + capTokens.join()}" { Object arg ->
                    delegate.set( theProp, arg )
                }
                println "column ${theProp} is a ${columnMap[theProp].getTypeName()}"
                switch ( columnMap[ theProp ].getTypeName() ) {
                    case "int":
                        println "Making static method findWhere${capTokens.join()}GreaterThan"
                        nextClass.metaClass.static."${'findWhere' + capTokens.join() + 'GreaterThan'}" = { int arg ->
                            nextClass.where( "${theProp} > ?", arg )
                        }
                        println "Making static method findWhere${capTokens.join()}LessThan"
                        nextClass.metaClass.static."${'findWhere' + capTokens.join() + 'LessThan'}" = { int arg ->
                            nextClass.where( "${theProp} < ?", arg )
                        }
                        println "Making static method findWhere${capTokens.join()}Between"
                        nextClass.metaClass.static."${'findWhere' + capTokens.join() + 'Between'}" = { int smaller, int larger ->
                            nextClass.where( "${theProp} between ? and ?", smaller, larger )
                        }
                        break
                    case "varchar":
                        println "Making static method findBy${capTokens.join()}"
                        nextClass.metaClass.static."${'findBy' + capTokens.join()}" = { String arg ->
                            nextClass.where( "${theProp} = ?", arg )
                        }
                        break
                }
            } // end each prop
     
        } // classList.each
        
    } // createNewMethods
}

Some more complex queries were still kind of awkward in ActiveJDBC. To find the books written by authors from Scotland, you need to do this:

Book.where('author_id in (select id from book_author where country = ?)', 'Scotland')

I just get the feeling there should be a cleaner way to do that.

You can find the code on github.

Image from “Evangelia quattuor [Évangiles dits de Hurault] (1r-216r). Capitulare evangeliorum (217r-234r)”, a 9th century manuscript housed at the Bibliothèque nationale de France. Source gallica.bnf.fr / BnF; image assumed allowed under Fair Use.