(Quick Reference)

8 Advanced GORM Features

Version: 2023.3.0-SNAPSHOT

8 Advanced GORM Features

The following sections cover more advanced usages of GORM including caching, custom mapping and events.

8.1 Events and Auto Timestamping

GORM supports the registration of events as methods that get fired when certain events occurs such as deletes, inserts and updates. The following is a list of supported events:

  • beforeInsert - Executed before an object is initially persisted to the database. If you return false, the insert will be cancelled.

  • beforeUpdate - Executed before an object is updated. If you return false, the update will be cancelled.

  • beforeDelete - Executed before an object is deleted. If you return false, the operation delete will be cancelled.

  • beforeValidate - Executed before an object is validated

  • afterInsert - Executed after an object is persisted to the database

  • afterUpdate - Executed after an object has been updated

  • afterDelete - Executed after an object has been deleted

  • onLoad - Executed when an object is loaded from the database

To add an event simply register the relevant method with your domain class.

Do not attempt to flush the session within an event (such as with obj.save(flush:true)). Since events are fired during flushing this will cause a StackOverflowError.

The beforeInsert event

Fired before an object is saved to the database

class Person {
   private static final Date NULL_DATE = new Date(0)

   String firstName
   String lastName
   Date signupDate = NULL_DATE

   def beforeInsert() {
      if (signupDate == NULL_DATE) {
         signupDate = new Date()
      }
   }
}

The beforeUpdate event

Fired before an existing object is updated

class Person {

   def securityService

   String firstName
   String lastName
   String lastUpdatedBy

   static constraints = {
      lastUpdatedBy nullable: true
   }

   def beforeUpdate() {
      lastUpdatedBy = securityService.currentAuthenticatedUsername()
   }
}

The beforeDelete event

Fired before an object is deleted.

class Person {
   String name

   def beforeDelete() {
      ActivityTrace.withNewSession {
         new ActivityTrace(eventName: "Person Deleted", data: name).save()
      }
   }
}

Notice the usage of withNewSession method above. Since events are triggered whilst Hibernate is flushing using persistence methods like save() and delete() won’t result in objects being saved unless you run your operations with a new Session.

Fortunately the withNewSession method lets you share the same transactional JDBC connection even though you’re using a different underlying Session.

The beforeValidate event

Fired before an object is validated.

class Person {
   String name

   static constraints = {
       name size: 5..45
   }

   def beforeValidate() {
       name = name?.trim()
   }
}

The beforeValidate method is run before any validators are run.

Validation may run more often than you think. It is triggered by the validate() and save() methods as you’d expect, but it is also typically triggered just before the view is rendered as well. So when writing beforeValidate() implementations, make sure that they can handle being called multiple times with the same property values.

GORM supports an overloaded version of beforeValidate which accepts a List parameter which may include the names of the properties which are about to be validated. This version of beforeValidate will be called when the validate method has been invoked and passed a List of property names as an argument.

class Person {
   String name
   String town
   Integer age

   static constraints = {
       name size: 5..45
       age range: 4..99
   }

   def beforeValidate(List propertiesBeingValidated) {
      // do pre validation work based on propertiesBeingValidated
   }
}

def p = new Person(name: 'Jacob Brown', age: 10)
p.validate(['age', 'name'])
Note that when validate is triggered indirectly because of a call to the save method that the validate method is being invoked with no arguments, not a List that includes all of the property names.

Either or both versions of beforeValidate may be defined in a domain class. GORM will prefer the List version if a List is passed to validate but will fall back on the no-arg version if the List version does not exist. Likewise, GORM will prefer the no-arg version if no arguments are passed to validate but will fall back on the List version if the no-arg version does not exist. In that case, null is passed to beforeValidate.

The onLoad/beforeLoad event

Fired immediately before an object is loaded from the database:

class Person {
   String name
   Date dateCreated
   Date lastUpdated

   def onLoad() {
      log.debug "Loading ${id}"
   }
}

beforeLoad() is effectively a synonym for onLoad(), so only declare one or the other.

The afterLoad event

Fired immediately after an object is loaded from the database:

class Person {
   String name
   Date dateCreated
   Date lastUpdated

   def afterLoad() {
      name = "I'm loaded"
   }
}

Custom Event Listeners

To register a custom event listener you need to subclass AbstractPersistenceEventListener (in package org.grails.datastore.mapping.engine.event) and implement the methods onPersistenceEvent and supportsEventType. You also must provide a reference to the datastore to the listener. The simplest possible implementation can be seen below:

public MyPersistenceListener(final Datastore datastore) {
    super(datastore)
}

@Override
protected void onPersistenceEvent(final AbstractPersistenceEvent event) {
    switch(event.eventType) {
        case PreInsert:
            println "PRE INSERT \${event.entityObject}"
        break
        case PostInsert:
            println "POST INSERT \${event.entityObject}"
        break
        case PreUpdate:
            println "PRE UPDATE \${event.entityObject}"
        break;
        case PostUpdate:
            println "POST UPDATE \${event.entityObject}"
        break;
        case PreDelete:
            println "PRE DELETE \${event.entityObject}"
        break;
        case PostDelete:
            println "POST DELETE \${event.entityObject}"
        break;
        case PreLoad:
            println "PRE LOAD \${event.entityObject}"
        break;
        case PostLoad:
            println "POST LOAD \${event.entityObject}"
        break;
    }
}

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
    return true
}

The AbstractPersistenceEvent class has many subclasses (PreInsertEvent, PostInsertEvent etc.) that provide further information specific to the event. A cancel() method is also provided on the event which allows you to veto an insert, update or delete operation.

Once you have created your event listener you need to register it. If you are using Spring this can be done via the ApplicationContext:

HibernateDatastore datastore = applicationContext.getBean(HibernateDatastore)
applicationContext.addApplicationListener new MyPersistenceListener(datastore)

If you are not using Spring then you can register the event listener using the getApplicationEventPublisher() method:

HibernateDatastore datastore = ... // get a reference to the datastore
datastore.getApplicationEventPublisher()
         .addApplicationListener new MyPersistenceListener(datastore)

Hibernate Events

It is generally encouraged to use the non-Hibernate specific API described above, but if you need access to more detailed Hibernate events then you can define custom Hibernate-specific event listeners.

You can also register event handler classes in an application’s app/conf/spring/resources.groovy or in the doWithSpring closure in a plugin descriptor by registering a Spring bean named hibernateEventListeners. This bean has one property, listenerMap which specifies the listeners to register for various Hibernate events.

The values of the Map are instances of classes that implement one or more Hibernate listener interfaces. You can use one class that implements all of the required interfaces, or one concrete class per interface, or any combination. The valid Map keys and corresponding interfaces are listed here:

Name Interface

auto-flush

AutoFlushEventListener

merge

MergeEventListener

create

PersistEventListener

create-onflush

PersistEventListener

delete

DeleteEventListener

dirty-check

DirtyCheckEventListener

evict

EvictEventListener

flush

FlushEventListener

flush-entity

FlushEntityEventListener

load

LoadEventListener

load-collection

InitializeCollectionEventListener

lock

LockEventListener

refresh

RefreshEventListener

replicate

ReplicateEventListener

save-update

SaveOrUpdateEventListener

save

SaveOrUpdateEventListener

update

SaveOrUpdateEventListener

pre-load

PreLoadEventListener

pre-update

PreUpdateEventListener

pre-delete

PreDeleteEventListener

pre-insert

PreInsertEventListener

pre-collection-recreate

PreCollectionRecreateEventListener

pre-collection-remove

PreCollectionRemoveEventListener

pre-collection-update

PreCollectionUpdateEventListener

post-load

PostLoadEventListener

post-update

PostUpdateEventListener

post-delete

PostDeleteEventListener

post-insert

PostInsertEventListener

post-commit-update

PostUpdateEventListener

post-commit-delete

PostDeleteEventListener

post-commit-insert

PostInsertEventListener

post-collection-recreate

PostCollectionRecreateEventListener

post-collection-remove

PostCollectionRemoveEventListener

post-collection-update

PostCollectionUpdateEventListener

For example, you could register a class AuditEventListener which implements PostInsertEventListener, PostUpdateEventListener, and PostDeleteEventListener using the following in an application:

beans = {

   auditListener(AuditEventListener)

   hibernateEventListeners(HibernateEventListeners) {
      listenerMap = ['post-insert': auditListener,
                     'post-update': auditListener,
                     'post-delete': auditListener]
   }
}

or use this in a plugin:

def doWithSpring = {

   auditListener(AuditEventListener)

   hibernateEventListeners(HibernateEventListeners) {
      listenerMap = ['post-insert': auditListener,
                     'post-update': auditListener,
                     'post-delete': auditListener]
   }
}

Automatic timestamping

If you define a dateCreated property it will be set to the current date for you when you create new instances. Likewise, if you define a lastUpdated property it will be automatically be updated for you when you change persistent instances.

If this is not the behaviour you want you can disable this feature with:

class Person {
   Date dateCreated
   Date lastUpdated
   static mapping = {
      autoTimestamp false
   }
}
If you have nullable: false constraints on either dateCreated or lastUpdated, your domain instances will fail validation - probably not what you want. Omit constraints from these properties unless you disable automatic timestamping.

It is also possible to disable the automatic timestamping temporarily. This is most typically done in the case of a test where you need to define values for the dateCreated or lastUpdated in the past. It may also be useful for importing old data from other systems where you would like to keep the current values of the timestamps.

Timestamps can be temporarily disabled for all domains, a specified list of domains, or a single domain. To get started, you need to get a reference to the AutoTimestampEventListener. If you already have access to the datastore, you can execute the getAutoTimestampEventListener method. If you don’t have access to the datastore, inject the autoTimestampEventListener bean.

Once you have a reference to the event listener, you can execute withoutDateCreated, withoutLastUpdated, or withoutTimestamps. The withoutTimestamps method will temporarily disable both dateCreated and lastUpdated.

Example:

//Only the dateCreated property handling will be disabled for only the Foo domain
autoTimestampEventListener.withoutDateCreated(Foo) {
    new Foo(dateCreated: new Date() - 1).save(flush: true)
}

//Only the lastUpdated property handling will be disabled for only the Foo and Bar domains
autoTimestampEventListener.withoutLastUpdated(Foo, Bar) {
    new Foo(lastUpdated: new Date() - 1, bar: new Bar(lastUpdated: new Date() + 1)).save(flush: true)
}

//All timestamp property handling will be disabled for all domains
autoTimestampEventListener.withoutTimestamps {
    new Foo(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
    new Bar(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
    new FooBar(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
}
Because the timestamp handling is only disabled for the duration of the closure, you must flush the session during the closure execution!

8.2 Custom ORM Mapping

GORM domain classes can be mapped onto many legacy schemas with an Object Relational Mapping DSL (domain specific language). The following sections takes you through what is possible with the ORM DSL.

None of this is necessary if you are happy to stick to the conventions defined by GORM for table names, column names and so on. You only needs this functionality if you need to tailor the way GORM maps onto legacy schemas or configures caching

Custom mappings are defined using a static mapping block defined within your domain class:

class Person {
    ...
    static mapping = {
        version false
        autoTimestamp false
    }
}

You can also configure global mappings in application.groovy (or an external config file) using this setting:

grails.gorm.default.mapping = {
    version false
    autoTimestamp false
}

It has the same syntax as the standard mapping block but it applies to all your domain classes! You can then override these defaults within the mapping block of a domain class.

8.2.1 Table and Column Names

Table names

The database table name which the class maps to can be customized using the table method:

class Person {
    ...
    static mapping = {
        table 'people'
    }
}

In this case the class would be mapped to a table called people instead of the default name of person.

Column names

It is also possible to customize the mapping for individual columns onto the database. For example to change the name you can do:

class Person {

    String firstName

    static mapping = {
        table 'people'
        firstName column: 'First_Name'
    }
}

Here firstName is a dynamic method within the mapping Closure that has a single Map parameter. Since its name corresponds to a domain class persistent field, the parameter values (in this case just "column") are used to configure the mapping for that property.

Column type

GORM supports configuration of Hibernate types with the DSL using the type attribute. This includes specifying user types that implement the Hibernate org.hibernate.usertype.UserType interface, which allows complete customization of how a type is persisted. As an example if you had a PostCodeType you could use it as follows:

class Address {

    String number
    String postCode

    static mapping = {
        postCode type: PostCodeType
    }
}

Alternatively if you just wanted to map it to one of Hibernate’s basic types other than the default chosen by GORM you could use:

class Address {

    String number
    String postCode

    static mapping = {
        postCode type: 'text'
    }
}

This would make the postCode column map to the default large-text type for the database you’re using (for example TEXT or CLOB).

See the Hibernate documentation regarding Basic Types for further information.

Many-to-One/One-to-One Mappings

In the case of associations it is also possible to configure the foreign keys used to map associations. In the case of a many-to-one or one-to-one association this is exactly the same as any regular column. For example consider the following:

class Person {

    String firstName
    Address address

    static mapping = {
        table 'people'
        firstName column: 'First_Name'
        address column: 'Person_Address_Id'
    }
}

By default the address association would map to a foreign key column called address_id. By using the above mapping we have changed the name of the foreign key column to Person_Adress_Id.

One-to-Many Mapping

With a bidirectional one-to-many you can change the foreign key column used by changing the column name on the many side of the association as per the example in the previous section on one-to-one associations. However, with unidirectional associations the foreign key needs to be specified on the association itself. For example given a unidirectional one-to-many relationship between Person and Address the following code will change the foreign key in the address table:

class Person {

    String firstName

    static hasMany = [addresses: Address]

    static mapping = {
        table 'people'
        firstName column: 'First_Name'
        addresses column: 'Person_Address_Id'
    }
}

If you don’t want the column to be in the address table, but instead some intermediate join table you can use the joinTable parameter:

class Person {

    String firstName

    static hasMany = [addresses: Address]

    static mapping = {
        table 'people'
        firstName column: 'First_Name'
        addresses joinTable: [name: 'Person_Addresses',
                              key: 'Person_Id',
                              column: 'Address_Id']
    }
}

Many-to-Many Mapping

GORM, by default maps a many-to-many association using a join table. For example consider this many-to-many association:

class Group {
    ...
    static hasMany = [people: Person]
}
class Person {
    ...
    static belongsTo = Group
    static hasMany = [groups: Group]
}

In this case GORM will create a join table called group_person containing foreign keys called person_id and group_id referencing the person and group tables. To change the column names you can specify a column within the mappings for each class.

class Group {
   ...
   static mapping = {
       people column: 'Group_Person_Id'
   }
}
class Person {
   ...
   static mapping = {
       groups column: 'Group_Group_Id'
   }
}

You can also specify the name of the join table to use:

class Group {
   ...
   static mapping = {
       people column: 'Group_Person_Id',
              joinTable: 'PERSON_GROUP_ASSOCIATIONS'
   }
}
class Person {
   ...
   static mapping = {
       groups column: 'Group_Group_Id',
              joinTable: 'PERSON_GROUP_ASSOCIATIONS'
   }
}

8.2.2 Caching Strategy

Setting up caching

Hibernate features a second-level cache with a customizable cache provider. This needs to be configured in the app/conf/application.yml file as follows:

hibernate:
  cache:
    use_second_level_cache: true
    provider_class: net.sf.ehcache.hibernate.EhCacheProvider
    region:
       factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory

You can customize any of these settings, for example to use a distributed caching mechanism.

For further reading on caching and in particular Hibernate’s second-level cache, refer to the Hibernate documentation on the subject.

Caching instances

Call the cache method in your mapping block to enable caching with the default settings:

class Person {
    ...
    static mapping = {
        table 'people'
        cache true
    }
}

This will configure a 'read-write' cache that includes both lazy and non-lazy properties. You can customize this further:

class Person {
    ...
    static mapping = {
        table 'people'
        cache usage: 'read-only', include: 'non-lazy'
    }
}

Caching associations

As well as the ability to use Hibernate’s second level cache to cache instances you can also cache collections (associations) of objects. For example:

class Person {

    String firstName

    static hasMany = [addresses: Address]

    static mapping = {
        table 'people'
        version false
        addresses column: 'Address', cache: true
    }
}
class Address {
    String number
    String postCode
}

This will enable a 'read-write' caching mechanism on the addresses collection. You can also use:

cache: 'read-write' // or 'read-only' or 'transactional'

to further configure the cache usage.

Caching Queries

In order for the results of queries to be cached, you must enable caching in your mapping:

hibernate:
  cache:
    use_query_cache: true

To enable query caching for all queries created by dynamic finders, GORM etc. you can specify:

hibernate:
  cache:
    queries: true    # This implicitly sets  `use_query_cache=true`

You can cache queries such as dynamic finders and criteria. To do so using a dynamic finder you can pass the cache argument:

def person = Person.findByFirstName("Fred", [cache: true])

You can also cache criteria queries:

def people = Person.withCriteria {
    like('firstName', 'Fr%')
    cache true
}

Cache usages

Below is a description of the different cache settings and their usages:

  • read-only - If your application needs to read but never modify instances of a persistent class, a read-only cache may be used.

  • read-write - If the application needs to update data, a read-write cache might be appropriate.

  • nonstrict-read-write - If the application only occasionally needs to update data (i.e. if it is very unlikely that two transactions would try to update the same item simultaneously) and strict transaction isolation is not required, a nonstrict-read-write cache might be appropriate.

  • transactional - The transactional cache strategy provides support for fully transactional cache providers such as JBoss TreeCache. Such a cache may only be used in a JTA environment and you must specify hibernate.transaction.manager_lookup_class in the app/conf/application.groovy file’s hibernate config.

8.2.3 Inheritance Strategies

By default GORM classes use table-per-hierarchy inheritance mapping. This has the disadvantage that columns cannot have a NOT-NULL constraint applied to them at the database level. If you would prefer to use a table-per-subclass inheritance strategy you can do so as follows:

class Payment {
    Integer amount

    static mapping = {
        tablePerHierarchy false
    }
}

class CreditCardPayment extends Payment {
    String cardNumber
}

The mapping of the root Payment class specifies that it will not be using table-per-hierarchy mapping for all child classes.

8.2.4 Custom Database Identity

You can customize how GORM generates identifiers for the database using the DSL. By default GORM relies on the native database mechanism for generating ids. This is by far the best approach, but there are still many schemas that have different approaches to identity.

To deal with this Hibernate defines the concept of an id generator. You can customize the id generator and the column it maps to as follows:

class Person {
    ...
    static mapping = {
        table 'people'
        version false
        id generator: 'hilo',
           params: [table: 'hi_value',
                    column: 'next_value',
                    max_lo: 100]
    }
}

In this case we’re using one of Hibernate’s built in 'hilo' generators that uses a separate table to generate ids.

For more information on the different Hibernate generators refer to the Hibernate reference documentation

Although you don’t typically specify the id field (GORM adds it for you) you can still configure its mapping like the other properties. For example to customise the column for the id property you can do:

class Person {
    ...
    static mapping = {
        table 'people'
        version false
        id column: 'person_id'
    }
}

8.2.5 Composite Primary Keys

GORM supports the concept of composite identifiers (identifiers composed from 2 or more properties). It is not an approach we recommend, but is available to you if you need it:

import org.apache.commons.lang.builder.HashCodeBuilder

class Person implements Serializable {

    String firstName
    String lastName

    boolean equals(other) {
        if (!(other instanceof Person)) {
            return false
        }

        other.firstName == firstName && other.lastName == lastName
    }

    int hashCode() {
        def builder = new HashCodeBuilder()
        builder.append firstName
        builder.append lastName
        builder.toHashCode()
    }

    static mapping = {
        id composite: ['firstName', 'lastName']
    }
}

The above will create a composite id of the firstName and lastName properties of the Person class. To retrieve an instance by id you use a prototype of the object itself:

def p = Person.get(new Person(firstName: "Fred", lastName: "Flintstone"))
println p.firstName

Domain classes mapped with composite primary keys must implement the Serializable interface and override the equals and hashCode methods, using the properties in the composite key for the calculations. The example above uses a HashCodeBuilder for convenience but it’s fine to implement it yourself.

Another important consideration when using composite primary keys is associations. If for example you have a many-to-one association where the foreign keys are stored in the associated table then 2 columns will be present in the associated table.

For example consider the following domain class:

class Address {
    Person person
}

In this case the address table will have an additional two columns called person_first_name and person_last_name. If you wish the change the mapping of these columns then you can do so using the following technique:

class Address {
    Person person
    static mapping = {
        columns {
                    person {
                column name: "FirstName"
                column name: "LastName"
                        }
        }
    }
}

8.2.6 Database Indices

To get the best performance out of your queries it is often necessary to tailor the table index definitions. How you tailor them is domain specific and a matter of monitoring usage patterns of your queries. With GORM’s DSL you can specify which columns are used in which indexes:

class Person {
    String firstName
    String address
    static mapping = {
        table 'people'
        version false
        id column: 'person_id'
        firstName column: 'First_Name', index: 'Name_Idx'
        address column: 'Address', index: 'Name_Idx,Address_Index'
    }
}

Note that you cannot have any spaces in the value of the index attribute; in this example index:'Name_Idx, Address_Index' will cause an error.

8.2.7 Optimistic Locking and Versioning

As discussed in the section on Optimistic and Pessimistic Locking, by default GORM uses optimistic locking and automatically injects a version property into every class which is in turn mapped to a version column at the database level.

If you’re mapping to a legacy schema that doesn’t have version columns (or there’s some other reason why you don’t want/need this feature) you can disable this with the version method:

class Person {
    ...
    static mapping = {
        table 'people'
        version false
    }
}
If you disable optimistic locking you are essentially on your own with regards to concurrent updates and are open to the risk of users losing data (due to data overriding) unless you use pessimistic locking

Version columns types

By default GORM maps the version property as a Long that gets incremented by one each time an instance is updated. But Hibernate also supports using a Timestamp, for example:

import java.sql.Timestamp

class Person {

    ...
    Timestamp version

    static mapping = {
        table 'people'
    }
}

There’s a slight risk that two updates occurring at nearly the same time on a fast server can end up with the same timestamp value but this risk is very low. One benefit of using a Timestamp instead of a Long is that you combine the optimistic locking and last-updated semantics into a single column.

8.2.8 Eager and Lazy Fetching

Lazy Collections

As discussed in the section on Eager and Lazy fetching, GORM collections are lazily loaded by default but you can change this behaviour with the ORM DSL. There are several options available to you, but the most common ones are:

  • lazy: false

  • fetch: 'join'

and they’re used like this:

class Person {

    String firstName
    Pet pet

    static hasMany = [addresses: Address]

    static mapping = {
        addresses lazy: false
        pet fetch: 'join'
    }
}
class Address {
    String street
    String postCode
}
class Pet {
    String name
}

The first option, lazy: false , ensures that when a Person instance is loaded, its addresses collection is loaded at the same time with a second SELECT. The second option is basically the same, except the collection is loaded with a JOIN rather than another SELECT. Typically you want to reduce the number of queries, so fetch: 'join' is the more appropriate option. On the other hand, it could feasibly be the more expensive approach if your domain model and data result in more and larger results than would otherwise be necessary.

For more advanced users, the other settings available are:

  • batchSize: N

  • lazy: false, batchSize: N

where N is an integer. These let you fetch results in batches, with one query per batch. As a simple example, consider this mapping for Person:

class Person {

    String firstName
    Pet pet

    static mapping = {
        pet batchSize: 5
    }
}

If a query returns multiple Person instances, then when we access the first pet property, Hibernate will fetch that Pet plus the four next ones. You can get the same behaviour with eager loading by combining batchSize with the lazy: false option.

You can find out more about these options in the Hibernate user guide. Note that ORM DSL does not currently support the "subselect" fetching strategy.

Lazy Single-Ended Associations

In GORM, one-to-one and many-to-one associations are by default lazy. Non-lazy single ended associations can be problematic when you load many entities because each non-lazy association will result in an extra SELECT statement. If the associated entities also have non-lazy associations, the number of queries grows significantly!

Use the same technique as for lazy collections to make a one-to-one or many-to-one association non-lazy/eager:

class Person {
    String firstName
}
class Address {

    String street
    String postCode

    static belongsTo = [person: Person]

    static mapping = {
        person lazy: false
    }
}

Here we configure GORM to load the associated Person instance (through the person property) whenever an Address is loaded.

Lazy Associations and Proxies

Hibernate uses runtime-generated proxies to facilitate single-ended lazy associations; Hibernate dynamically subclasses the entity class to create the proxy.

Consider the previous example but with a lazily-loaded person association: Hibernate will set the person property to a proxy that is a subclass of Person. When you call any of the getters (except for the id property) or setters on that proxy, Hibernate will load the entity from the database.

Unfortunately this technique can produce surprising results. Consider the following example classes:

class Pet {
    String name
}
class Dog extends Pet {
}
class Person {
    String name
    Pet pet
}

Proxies can have confusing behavior when combined with inheritance. Because the proxy is only a subclass of the parent class, any attempt to cast or access data on the subclass will fail. Assuming we have a single Person instance with a Dog as the pet.

The code below will not fail because directly querying the Pet table does not require the resulting objects to be proxies because they are not lazy.

def pet = Pet.get(1)
assert pet instanceof Dog

The following code will fail because the association is lazy and the pet instance is a proxy.

def person = Person.get(1)
assert person.pet instanceof Dog

If the only goal is to check if the proxy is an instance of a class, there is one helper method available to do so that works with proxies. Take special care in using it though because it does cause a call to the database to retrieve the association data.

def person = Person.get(1)
assert person.pet.instanceOf(Dog)

There are a couple of ways to approach this issue. The first rule of thumb is that if it is known ahead of time that the association data is required, join the data in the query of the Person. For example, the following assertion is true.

def person = Person.where { id == 1 }.join("pet").get()
assert person.pet instanceof Dog

In the above example the pet association is no longer lazy because it is being retrieved along with the Person and thus no proxies are necessary. There are cases when it makes sense for a proxy to be returned, mostly in the case where its impossible to know if the data will be used or not. For those cases in order to access properties of the subclasses, the proxy must be unwrapped. To unwrap a proxy inject an instance of ProxyHandler and pass the proxy to the unwrap method.

def person = Person.get(1)
assert proxyHandler.unwrap(person.pet) instanceof Dog

For cases where dependency injection is impractical or not available, a helper method GrailsHibernateUtil.unwrapIfProxy(Object) can be used instead.

Unwrapping a proxy is different than initializing it. Initializing a proxy simply populates the underlying instance with data from the database, however unwrapping a returns the inner target.

8.2.9 Custom Cascade Behaviour

As described in the section on cascading updates, the primary mechanism to control the way updates and deletes cascade from one association to another is the static belongsTo property.

However, the ORM DSL gives you complete access to Hibernate’s transitive persistence capabilities using the cascade attribute.

Valid settings for the cascade attribute include:

  • merge - merges the state of a detached association

  • save-update - cascades only saves and updates to an association

  • delete - cascades only deletes to an association

  • lock - useful if a pessimistic lock should be cascaded to its associations

  • refresh - cascades refreshes to an association

  • evict - cascades evictions (equivalent to discard() in GORM) to associations if set

  • all - cascade all operations to associations

  • all-delete-orphan - Applies only to one-to-many associations and indicates that when a child is removed from an association then it should be automatically deleted. Children are also deleted when the parent is.

To specify the cascade attribute simply define one or more (comma-separated) of the aforementioned settings as its value:

class Person {

    String firstName

    static hasMany = [addresses: Address]

    static mapping = {
        addresses cascade: "all-delete-orphan"
    }
}
class Address {
    String street
    String postCode
}

8.2.10 Custom Hibernate Types

You saw in an earlier section that you can use composition (with the embedded property) to break a table into multiple objects. You can achieve a similar effect with Hibernate’s custom user types. These are not domain classes themselves, but plain Java or Groovy classes. Each of these types also has a corresponding "meta-type" class that implements org.hibernate.usertype.UserType.

The Hibernate reference manual has some information on custom types, but here we will focus on how to map them in GORM. Let’s start by taking a look at a simple domain class that uses an old-fashioned (pre-Java 1.5) type-safe enum class:

class Book {

    String title
    String author
    Rating rating

    static mapping = {
        rating type: RatingUserType
    }
}

All we have done is declare the rating field the enum type and set the property’s type in the custom mapping to the corresponding UserType implementation. That’s all you have to do to start using your custom type. If you want, you can also use the other column settings such as "column" to change the column name and "index" to add it to an index.

Custom types aren’t limited to just a single column - they can be mapped to as many columns as you want. In such cases you explicitly define in the mapping what columns to use, since Hibernate can only use the property name for a single column. Fortunately, GORM lets you map multiple columns to a property using this syntax:

class Book {

    String title
    Name author
    Rating rating

    static mapping = {
        author type: NameUserType, {
            column name: "first_name"
            column name: "last_name"
        }
        rating type: RatingUserType
    }
}

The above example will create "first_name" and "last_name" columns for the author property. You’ll be pleased to know that you can also use some of the normal column/property mapping attributes in the column definitions. For example:

column name: "first_name", index: "my_idx", unique: true

The column definitions do not support the following attributes: type, cascade, lazy, cache, and joinTable.

One thing to bear in mind with custom types is that they define the SQL types for the corresponding database columns. That helps take the burden of configuring them yourself, but what happens if you have a legacy database that uses a different SQL type for one of the columns? In that case, override the column’s SQL type using the sqlType attribute:

class Book {

    String title
    Name author
    Rating rating

    static mapping = {
        author type: NameUserType, {
            column name: "first_name", sqlType: "text"
            column name: "last_name", sqlType: "text"
        }
        rating type: RatingUserType, sqlType: "text"
    }
}

Mind you, the SQL type you specify needs to still work with the custom type. So overriding a default of "varchar" with "text" is fine, but overriding "text" with "yes_no" isn’t going to work.

8.2.11 Derived Properties

A derived property is one that takes its value from a SQL expression, often but not necessarily based on the value of one or more other persistent properties. Consider a Product class like this:

class Product {
    Float price
    Float taxRate
    Float tax
}

If the tax property is derived based on the value of price and taxRate properties then is probably no need to persist the tax property. The SQL used to derive the value of a derived property may be expressed in the ORM DSL like this:

class Product {
    Float price
    Float taxRate
    Float tax

    static mapping = {
        tax formula: 'PRICE * TAX_RATE'
    }
}

Note that the formula expressed in the ORM DSL is SQL so references to other properties should relate to the persistence model not the object model, which is why the example refers to PRICE and TAX_RATE instead of price and taxRate.

With that in place, when a Product is retrieved with something like Product.get(42), the SQL that is generated to support that will look something like this:

select
    product0_.id as id1_0_,
    product0_.version as version1_0_,
    product0_.price as price1_0_,
    product0_.tax_rate as tax4_1_0_,
    product0_.PRICE * product0_.TAX_RATE as formula1_0_
from
    product product0_
where
    product0_.id=?

Since the tax property is derived at runtime and not stored in the database it might seem that the same effect could be achieved by adding a method like getTax() to the Product class that simply returns the product of the taxRate and price properties. With an approach like that you would give up the ability query the database based on the value of the tax property. Using a derived property allows exactly that. To retrieve all Product objects that have a tax value greater than 21.12 you could execute a query like this:

Product.findAllByTaxGreaterThan(21.12)

Derived properties may be referenced in the Criteria API:

Product.withCriteria {
    gt 'tax', 21.12f
}

The SQL that is generated to support either of those would look something like this:

select
    this_.id as id1_0_,
    this_.version as version1_0_,
    this_.price as price1_0_,
    this_.tax_rate as tax4_1_0_,
    this_.PRICE * this_.TAX_RATE as formula1_0_
from
    product this_
where
    this_.PRICE * this_.TAX_RATE>?
Because the value of a derived property is generated in the database and depends on the execution of SQL code, derived properties may not have GORM constraints applied to them. If constraints are specified for a derived property, they will be ignored.

8.2.12 Custom Naming Strategy

By default GORM uses Hibernate’s ImprovedNamingStrategy to convert domain class Class and field names to SQL table and column names by converting from camel-cased Strings to ones that use underscores as word separators. You can customize these on a per-class basis in the mapping closure but if there’s a consistent pattern you can specify a different NamingStrategy class to use.

Configure the class name to be used in app/conf/application.groovy in the hibernate section, e.g.

dataSource {
    pooled = true
    dbCreate = "create-drop"
    ...
}

hibernate {
    cache.use_second_level_cache = true
    ...
    naming_strategy = com.myco.myproj.CustomNamingStrategy
}

You can also specify the name of the class and it will be loaded for you:

hibernate {
    ...
    naming_strategy = 'com.myco.myproj.CustomNamingStrategy'
}

A third option is to provide an instance if there is some configuration required beyond calling the default constructor:

hibernate {
    ...
    def strategy = new com.myco.myproj.CustomNamingStrategy()
    // configure as needed
    naming_strategy = strategy
}

You can use an existing class or write your own, for example one that prefixes table names and column names:

package com.myco.myproj

import org.hibernate.cfg.ImprovedNamingStrategy
import org.hibernate.util.StringHelper

class CustomNamingStrategy extends ImprovedNamingStrategy {

    String classToTableName(String className) {
        "table_" + StringHelper.unqualify(className)
    }

    String propertyToColumnName(String propertyName) {
        "col_" + StringHelper.unqualify(propertyName)
    }
}

8.3 Default Sort Order

You can sort objects using query arguments such as those found in the list method:

def airports = Airport.list(sort:'name')

However, you can also declare the default sort order for a collection in the mapping:

class Airport {
    ...
    static mapping = {
        sort "name"
    }
}

The above means that all collections of Airport instances will by default be sorted by the airport name. If you also want to change the sort order, use this syntax:

class Airport {
    ...
    static mapping = {
        sort name: "desc"
    }
}

Finally, you can configure sorting at the association level:

class Airport {
    ...
    static hasMany = [flights: Flight]

    static mapping = {
        flights sort: 'number', order: 'desc'
    }
}

In this case, the flights collection will always be sorted in descending order of flight number.

These mappings will not work for default unidirectional one-to-many or many-to-many relationships because they involve a join table. See this issue for more details. Consider using a SortedSet or queries with sort parameters to fetch the data you need.