Tuesday, October 20, 2009

Session Notes - A Practical Take on GORM

Presenter: Robert Fischer
Author: Grails Persistence with GORM and GSQL
SmokeJumperIT -- Grails consulting and training


  • Overview of GORM

  • Intro to HQL

  • tour of GORM labs

  • tour of GORM plugins

  • extending GORM

GORM 101

  • Grails Object Relational Mapping

  • extensible and dynamic data access API

    • dynamic -- as you describe to grails what's in your application, you get more functionality automatically

  • hibernate wrapper (sometimes)

    • can use GORM outside of grails

    • transaction handling

    • data cache

    • schema generation/update system

  • even with the no SQL movement you still need an object to something mapping

Basic GORM Conventions

  • ./grails-app/domain

  • domain classes define tables

  • instance properties define columns

  • static properties define configuration

    • e.g. static hasMany = [bazzes:Baz], static mapping = { ... }

    • any time you see this, some sort of Grails magic is going to be happening behind the scenes

  • automatic id and version

    • GORM assumes you want an artificial ID and you want to work with optimistic locking

    • natural keys are enticing, but always become a problem

    • can opt out of these, but not encouraged

  • opt-in timestamping using dateCreated and lastUpdated

  • everything beyond these basics is probably boundary conditions or a need to extend GORM in some way

GORM Classes (Domain Classes)

  • even with a simple class Baz {} declaration you get tons of functionality

  • better to err on the side of having a domain class for everything in your app to get counts, lists, findByVersion, etc.

  • constraints used for data integrity

    • note that if you're using existing data you can pull "bad" data back but when you try to save, the constraints will kick in and fail so you'll have to deal with the bad data at that point

  • mapping -- depending on how you define things may or may not get mapped

    • String foo (declared string) = mapped

    • bar (only defined via its getter and setter but not declared) = mapped

      • this will always be dirty

      • can come in handy for implementing transforms in an overridden setter

    • bar2 (default artificial property) = not mapped

      • can come in handy for tracking schema changes over time without having this reflected in the database

    • foo2 (default natural property) = not mapped

      • the default part is the problem, not artificial vs. natural

    • baz (runtime added property) = not mapped

      • at application startup grails looks at the compiled structure of the classes to generate hibernate mappings

Underappreciated Methods

  • Foo.lock(id)

    • pulls an object back and locks it from being updated from other processes

    • can solve transactional issues

    • does for update in oracle, currently does nothing in mysql (but supposedly this is getting implemented)

  • Foo.read(id)

    • pulls a record from the database and says "I'm not going to modify this"

    • if changes are made on the object after read, they're ignored

    • saves cache since it won't be cached

  • Foo.findAllWhere(bar:'one',baz:true)

    • can be implemented as Foo.findAllByBarAndBaz('one', true) but is more concise

  • Foo.executeQuery(hql) and Foo.executeUpdate(hql)

    • can use executeUpdate on update, delete, insert

    • allows you to take a bit more ownership of what's going on with the database and the generated sql

Hibernate Query Language (HQL)

  • like SQL but with a lot less pain

    • working directly with objects and relationships as opposed to having to write sql joins manually

    • if you're finding yourself writing sql and manually building up objects, chances are hql can handle what you're doing more simply

  • leverages mapped classes and properties

  • provides abilities to construct objects and maps directly in queries

  • one major limitation: error reporting is miserable

    • if you screw up your hql statement in a way that makes sense to the parser, error messages are good

    • if the hql doesn't make sense to the parser, you just get a null pointer exception from somewhere deep in hibernate

    • hibernate team is aware of this and working on improving

HQL for SQL-heads

  • query on objects, not on database tables

  • objects are normally returned

  • select * can be left off (just "from Foo")

  • no need to join *-to-one relationships: from Foo f where f.bar = ?

  • traverse many-to-many relationships via a joining on object relationships

    • from Foo f join f.bazzes b

    • can say "fetch join" to eagerly pull back bazzes for Foo

  • on existing databases--can use GREG (or GRAG?) -- Grails Application Generator

    • generates domain classes, etc. from an existing database

    • falls down if some company mandates all db access is through storedprocs

      • problem for Hibernate in general

      • fighting against the GORM conventions so hard that it makes it very difficult

      • plugin available to specify different databases for different domain objects--can get around some of this going this route

HQL Tips and Tricks

  • encapsulate the HQL in methods

    • don't put it in a service--put it on the domain class where it belongs

    • HQL is referencing properties, so if the property name changes you have to track all references down if this isn't in the domain class

  • use $Token.name to save having to write out package names

    • standard groovy references--resolves the same way

  • use simple interpolation (${}) to add a bit of dynamism to your query

  • do not ever directly interpolate external values into hql

    • sql injection attack vector

    • always pass these in as parameters


  • plugin

  • tons of added methods to GORM

    • Foo.query -- shorter reference for HQL queries

    • for any property on a class you get

      • min, max, avg, sum, count, countDistinct on each property (e.g. Foo.minBar, Foo.maxBar, etc.)

    • Foo.connection -- access to current db connection

    • Foo.sql

    • Foo.session

    • Foo.flush()

    • $foo.errors -- nicer representation of errors

    • Foo.withCriteria -- allows ordering by nested properties (only works for one to one properties)

    • Gorm.connection, Gorm.flush, etc.

      • makes calls directly on gorm without having to refer to an arbitrary domain class

    • transactional actions in controllers

      • got him flamed on the list since this is supposed to be in services, but many people do db stuff in controllers

    • HasMany pagination/counts
      foo.bars(max:2, offset:3)

    • session object dehydration

      • if an object is thrown into the http session and it has an id, the id gets persisted in the session and it can be used to pull back the domain class if the object is attempted to be retrieved after the hibernate session has died

Favorite GORM Stunt: Transparent API Switch

  • assume there is a slow gorm api call
    Foo.findAllByBarOrBaz(bar, baz)

  • define a static method with the api call method name and hand-optimized sql
    def ids = Foo.sql.rows(query)*.id

  • return the (possibly cached) instances
    return Foo.getAll(ids)

Other GORM Plugins

  • audit logging

    • automatically keeps an audit trail on database activity

  • extended GORM mappings

  • GORM HBase (Hadoop HBase)


    • exists because of Google App Engine and the BigTable JPA functionality

  • explicit insert

    • allows you to do an explicit insert as opposed to save() which might do an insert or an update

  • datasources (per-domain class data source)

  • multitenant (per-tenant data source)

    • tenant attached to an HTTP request

    • great for situations where you have multiple clients each of which has their own datasource

  • system itools

    • plugin for IBM itools

  • DTO

  • joda-time

    • makes Joda-Time compatible with java.util.Date

    • makes all Joda-Time structures persistable to the database

Extending GORM The Easy Way

  • create plugin based on GORM Labs
    dependsOn = [gormLabs: '0 > *']

    • lets you take advantage of high-level stuff that GORM labs fixes

  • do metaClass mangling in doWithDynamicMethods

  • retrieve domain class metaClasses by application.domainClasses*.clazz

Current FOSS Projects - Shameless Cry for Help

  • Liquibase-DSL / Autobase

  • Bitescript (was JVMScript)

  • GORM Labs, Testing Labs, Sublog

  • JConch


  • interesting plugins for other frameworks/languages

    • PHP plugin -- Grails can wrap existing PHP apps

  • any cases where you wouldn't use GORM/Hibernate?

    • if storedprocs are required, not a good fit

    • in pretty much every other case hibernate works well

    • spent some time doing rails, perfer gorm method of doing things as opposed to activerecord

  • in production databases, what's the best practice for the datasource setting? (e.g. create, update, create-drop, etc.)

    • schema management is a difficult problem if you want 100% uptime

    • changing from one schema to another can cause some issues with hibernate

    • autobase plugin fixes some of these problems -- allows you to run migrations but have to write migrations by hand at this point

No comments: