CHAPTER 10

GORM

As you may have garnered from the table of contents for this book, with no fewer than three chapters dedicated to the subject, the persistence layer of Grails is a critical part of the picture. In Chapter 3, you obtained a surface-level understanding about what domain classes are and how they map onto the underlying database. In this chapter, you're going to plunge headfirst into understanding the inner workings of GORM.

As a starting point, you'll learn about the basic persistence operations involved in reading and writing objects from a database. After going through the basics, you'll then be taken through the semantics of GORM including as many corner cases and surprises as we're able to fit into a single chapter.

Persistence Basics

Fortunately, we won't be spending too long on the foundations, since you've already been exposed to the basic GORM methods as early as Chapter 2, where we took you through the generated scaffolding code. Since then, you've been exposed to various aspects of GORM from dynamic finders to the criteria queries used in Chapter 9.

Nevertheless, as a recap, let's take a look at the basic read operations provided by GORM.

Reading Objects

Each domain class is automatically enhanced, via metaprogramming, with a number of methods that allow you to query for domain instances. The simplest of these is the get method, which takes the identifier of a domain class and returns either an instance or null if no instance was found in the database. Listing 10-1 shows a simple example, highlighted in bold, from the AlbumController you've already developed.

Listing 10-1. Using the get Method

class AlbumController {
    def show = {
        def album = Album.get(params.id)
        if(album) {
         ...
     }
     ...
   }
}

In addition to the simple get method, there is also a getAll method that can take several identifiers and return a List of instances. You can specify the identifiers as a List or using varargs, for example:

def albums = Album.getAll(1,2,3)

When using the get method, the object is loaded from the database in a modifiable state. In other words, you can make changes to the object, which then get persisted to the database. If you want to load an object in a read-only state, you can use the read method instead:

def album = Album.read(params.id)

In this case, the Album instance returned cannot be modified. Any changes you make to the object will not be persisted.

Listing, Sorting, and Counting

A common way to retrieve items from a database is to simply list them. Clearly, it is not always desirable to list every instance, and the order in which they are returned is often important. GORM provides a list() method, which takes a number of named arguments such as max, offset, sort, and order to customize the results, the definitions of which are listed here:

  • max: The maximum number of instances to return
  • offset: The offset relative to 0 of the first result to return
  • sort: The property name to sort by
  • order: The order of the results, either asc or desc for ascending and descending order, respectively

In addition to list(), and often used in combination with it, is the count() method, which counts the total number of instances in the database. To demonstrate these, let's look at some examples of their usage, as shown in Listing 10-2.

Listing 10-2. Using the list() Method

// get all the albums; careful, there might be many!
def allAlbums = Album.list()
// get the ten most recently created albums
def topTen = Album.list(max:10, sort:'dateCreated', order:'desc')
// get the total number of albums
def totalAlbums = Album.count()

As you can imagine, it is fairly trivial to use the list() method to perform the pagination of results simply by customizing the offset argument. In addition, there is a set of listOrderBy* methods that are variations on the list() method.

The listOrderBy* methods provide an example where each method uses the properties on the class itself in the method signatures. They are unique to each domain class, but it is just a matter of understanding the convention to use them. Listing 10-3 presents an example that lists all Album instances, ordered by the dateCreated property, simply by invoking the listOrderByDateCreated()method.

Listing 10-3. Using listOrderBy

// all albums ordered by creation date
def allByDate = Album.listOrderByDateCreated()

Using standard bean conventions, the property name is appended to the end of the method signature starting with a capital letter. You'll see more examples of this later in the chapter when we cover dynamic finders, including a variation of the count method.

Saving, Updating, and Deleting

As you've seen already, objects can be persisted by calling the save() method. For example, the code in Listing 10-4 demonstrates how to persist an instance of the Album class, assuming it validates successfully.

Listing 10-4. Saving an Instance

def album = new Album(params)
album.save()

We'll have more to say about how GORM persists objects to the database in "The Semantics of GORM" later in this chapter. For now, all you need to know is that at some point the underlying Hibernate engine will execute a SQL INSERT to persist the Album instance to the database. Updating objects is strikingly similar because doing so involves calling the same save() method on an existing persistent instance, as shown in Listing 10-5.

Listing 10-5. Updating an Instance

def album = Album.get(1)
album.title = "The Changed Title"
album.save()

When the save() method is called, Hibernate automatically works out whether it should issue a SQL INSERT or a SQL UPDATE. Occasionally, on certain older databases, Hibernate may get this decision wrong and issue an UPDATE when it should be doing an INSERT. You can get around this by passing an explicit insert argument to the save() method:

album.save(insert:true)

As for deleting objects, this is done with the delete() method:

album.delete()

So, that's the simple stuff. Next you'll be looking in more detail at associations in GORM and how those work.

Associations

Chapter 3 already provided some detail on associations in GORM in their different incarnations, but there is a lot more to the associations that GORM supports. In a typical one-to-many association such as the songs property of the Album class, the type is a java.util.Set. If you recall the semantics of Set as defined by javadoc, they don't allow duplicates and have no order. However, you may want an association to have a particular order.

One option is to use a SortedSet, which requires you to implement the Comparable interface for any item placed into the SortedSet. For example, Listing 10-6 shows how to sort tracks by the trackNumber property.

Listing 10-6. Using SortedSet to Sort Associations

class Album {
    ...
    SortedSet songs
}
class Song implements Comparable {
    ..
    int compareTo(o) {
        if(this.trackNumber > o.trackNumber)
            return 1
        elseif(this.trackNumber < o.trackNumber)
            return −1
        return 0
    }
}

Alternatively, you can specify the sort order declaratively using the mapping property introduced in Chapter 3. For example, if you wanted to sort Song instances by track number for all queries, you can do so with the sort method:

class Song {
    ...
    static mapping = {
       sort "trackNumber"
    }
}

You may not want to sort by the trackNumber property for every query or association, in which case you can apply sorting to the songs association of the Album class only:

static mapping = {
    songs sort: "trackNumber"
}

Another way to change the way sorting is done is to use a different collection type such as java.util.List. Unlike a Set, a List allows duplicates and retains the order in which objects are placed into the List. To support List associations, Hibernate uses a special index column that contains the index of each item in the List. Listing 10-7 shows an example of using a List for the songs association.

Listing 10-7. Using a List Association

class Album {
    ...
    List songs
}

Unlike Set associations, which have no concept of order, with a List you can index into a specific entry, for example:

println album.songs[0]

Finally, GORM also supports Map associations where the key is a String. Simply change the type from List to Map in the example in Listing 10-7 and use a String instead of an Integer to access entries. For both List and Map collection types, Grails creates an index column. In the case of a List, the index column holds a numeric value that signifies its position in the List, while for a Map the index column holds the Map key.

Relationship Management Methods

As well as giving you the ability to map associations to the database, GORM also automatically provides you with methods to manage those associations. The addTo* and removeFrom* dynamic methods allow you to add and remove entries from an association. Additionally, both methods return the instance they are called on, thus allowing you to chain method calls. Listing 10-8 shows an example of using the addToSongs method of the Album class.

Listing 10-8. Using Relationship Management Methods

new Album(title:"Odelay",
          artist:beck
          year:1996)
            .addToSongs(title:"Devil's Haircut", artist:beck, duration:342343)
            .addToSongs(title:"Hotwax", artist:beck, duration:490583)
            ...
            save()

As you can see from the example, you can pass just the values of the Song as named parameters to the addToSongs method, and GORM will automatically instantiate a new instance and add it to the songs association. Alternatively, if you already have a Song instance, you can simply pass that into the addToSongs method.

Transitive Persistence

Whenever you save, update, or delete an instance in GORM, the operation can cascade to any associated objects. The default cascade behavior in GORM is dictated by the belongsTo property first discussed in Chapter 3. For example, if the Song class belongsTo the Album class, then whenever an Album instance is deleted, all of the associated Song instances are deleted too. If there is no belongsTo definition in an association, then saves and updates cascade, but deletes don't.

If you need more control over the cascading behavior, you can customize it using the cascade method of the mapping block, as shown in Listing 10-9.

Listing 10-9. Customizing the Cascading Behavior

class Album {
    ...
    static mapping = {
        songs cascade:'save-udpate'
    }
}

A special cascade style called delete-orphan exists for the case where you want a child object to be deleted if it is removed from an association but not deleted explicitly.


Tip For more information on the different cascade options available, take a look at the related section in the Hibernate documentation at http://www.hibernate.org/hib_docs/reference/en/html_single/#objectstate-transitive.


Querying

Pretty much every nontrivial application will need to query persistent data. With the underlying storage medium of choice being the database, the typical way to achieve this historically has been with SQL. Relational database systems with their tables and columns are significantly different enough from Java objects that abstracting data access has been a long-term struggle for many an ORM vendor.

Hibernate provides an elegant enough Java API for querying objects stored in a database, but GORM moves up to the next level by completely abstracting the majority of data access logic. Don't expect to see many dependencies on the org.hibernate package in your codebase, because GORM nicely abstracts the details of interaction with Hibernate. In the next few sections, we'll cover the different ways you can query with GORM, from dynamic finders to criteria GORM.

Dynamic Finders

Dynamic finders are among the most powerful concepts of GORM; as with the previously mentioned listOrderBy* method, they use the property names of the class to perform queries. However, they are even more flexible than this, because they allow logical queries such as And, Or, and Not to form so-called method expressions. There can be hundreds of combinations for any given class, but, again, they're fairly simple to remember if you know the convention. Let's look at an example findBy* method first, shown in Figure 10-1, which locates a unique instance for the specified method expression.

image

Figure 10-1. Basic dynamic finder syntax

The diagram uses the title and genre properties to look up an Album instance. There is a logical And expression in the middle to ensure both values need to be equal in the query. This could be replaced with a logical Or to look up a Album that either has a title of Beck or has a genre of Alternative.

We have, however, only brushed on what is possible with dynamic finders and method expressions. Dynamic finders support a wide range of expressions that allow GreaterThan/ LessThan, Like, and Between queries, to name just a few, simply by appending an additional expression on the end of the property name. Listing 10-10 shows some of these in action.

Listing 10-10. Dynamic Finders in Action

// retrieve an album where the title contains 'Shake'
def album = Album.findByTitleLike('%Shake%')
// get a album created in last 10 days
def today = new Date()
def last10Days = Album
                    .findByDateCreatedBetween(today-10,today)
// first album that is not 'Rock'
def somethingElse =    Album
                    .findByGenreNotEqual('Rock')

Table 10-1 illustrates all the possible expressions that can be appended, the number of arguments they expect, and an example of each in action.

Table 10-1 Available Dynamic Finder Method Expressions

Expression Arguments Example
Between 2 Album.findByDateCreatedBetween(today-10,today)
Equals 1 Album.findByTitleEquals('Aha Shake Heartbreak')
GreaterThan 1 Album.findByDateCreatedGreaterThan(lastMonth)
GreaterThanOrEqual 1 Album.findByDateCreatedGreaterThanOrEqual(lastMonth)
InList 1 Album.findByTitleInList(['Aha Shake Heartbreak', 'Odelay'])
IsNull 0 Album.findByGenreIsNull()
IsNotNull 0 Album.findByGenreIsNotNull()
LessThan 1 Album.findByDateCreatedLessThan(lastMonth)
LessThanOrEqual 1 Album.findByDateCreatedLessThanOrEqual(lastMonth)
Like 1 Album.findByTitleLike('Shake')
NotEqual 1 Album.findByTitleNotEqual('Odelay")

The findBy* method has two cousins that accept the same method expressions you've already seen. The first is findAllBy*, which retrieves all the instances that match the method expression as a java.util.List. Finally, there is the countBy* method that returns the total number of instances found by the method expression as an integer. It is worth opening up the Grails console, by typing grails console in a command window, and playing with these methods to experiment with the different combinations and discover just how easy they are to use.

You'll find that GORM's dynamic finders pretty much eliminate the need for a Data Access Object (DAO) layer, which you typically need in Java applications. Remember those? No? OK, well, the process is something like this:

  1. Define an interface for the data access logic. The signatures will look strikingly like the dynamic finder methods you've seen so far.
  2. Implement the interface using a Java class.
  3. Using Spring, or your IoC container of choice, to wire in dependencies such as the data source or Hibernate Session.

If you think about it, data access logic is extremely repetitive and heavily violates the DRY principles Grails is founded on. Luckily, with GORM and its dynamic finders, you can forget the DAO.

In the next section, you'll explore how Grails makes criteria more accessible via concise builder syntax.

Criteria Queries

Possibly one of the most powerful mechanisms for querying is with criteria. Criteria use a builder syntax for creating queries using Groovy's builder support. A builder in Groovy is essentially a hierarchy of method calls and closures that is perfect for "building" tree-like structures such as XML documents or a graphical user interface (GUI). Builders are also good candidates for constructing queries, particularly dynamic queries, which are often constructed with the horrifically error-prone StringBuffer.

The Hibernate Criteria API is meant to reduce the risk of errors by providing a program-matic way to construct "criteria" queries. However, Groovy's expressive syntax and powerful metaprogramming support has taken this to a new level of conciseness. Let's start by looking at basic usage patterns of criteria, after which we can move on to some more advanced examples.

Before you can perform a criteria query, you need a criteria instance for the class you want to query. To facilitate this, GORM provides a createCriteria static method on each domain class. Once you have acquired the criteria instance, one of four methods can be invoked, each of which expects a closure argument:

  • get: Locates a unique instance for the query
  • list: Returns a list of instances for the query
  • cscroll: Returns a ScrollableResults instance for the query
  • count: Returns the total results as an integer for the query

The most common use case is to use the list() method on the criteria instance to perform the query, as shown in Listing 10-11.

Listing 10-11. A Simple Criteria Query

def c = Album.createCriteria()
def results = c.list {
        eq('genre', 'Alternative')
        between('dateCreated', new Date()-30, new Date())
}

The previous example lists all the Album instances with a genre of Alternative created in the past 30 days. The nested method calls within the closure block translate into method calls on Hibernate's org.hibernate.criterion.Restrictions class, the API for which is too long to list here. Nevertheless, the eq and between methods shown here are just two of many for performing all the typical queries found in query languages such as SQL and HQL.

It is worth taking a look at the API on the Hibernate web site (http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html) to see what is available and to get a better understanding of the power that is at your fingertips. Of course, you can accomplish queries similar to those in Listing 10-11 with dynamic finder methods. What you haven't really explored is the power of closures and building the query up dynamically.

Consider for the moment that a closure is just a block of code and can be assigned to a variable. Also, consider that a closure can reference variables within its enclosing scope. Put the two together, and you have a pretty powerful mechanism for reusing dynamically constructed queries.

As an example, say you have a map whose keys define the property names to be queried, and the values define the value such as the params object provided by Grails controllers. A query could easily be built up from this map and assigned to a variable. Listing 10-12 provides an example of this concept in action.

Listing 10-12. Dynamic Querying with Criteria

1 def today = new Date()
2 def queryMap =   [ genre: 'Alternative', dateCreated: [today-10,today] ]
3 def query = {
4            // go through the query map
5            queryMap.each { key, value ->
6                        // if we have a list assume a between query
7                        if(value instanceof List) {
8                                // use the spread operator to invoke
9                                between(key, *value)
10                       }
11                       else {
12                              like(key,value)
13                       }
14            }
15 }
16
17 // create a criteria instance
18 def criteria = Album.createCriteria()
19
20 // count the results
21 println( criteria.count(query) )
22
23 // reuse again to get a unique result
24 println( criteria.get(query) )
25
26 // reuse again to list all
27 criteria.list(query).each { println it }
28
29 // use scrollable results
30 def scrollable = criteria.scroll(query)
31 def next = scrollable.next()
32 while(next) {
33         println(scrollable.getString('title'))
34         next = scrollable.next()
35 }

That's a fairly long example that includes some fairly advanced concepts. To simplify your understanding of it, we've included line numbers, and we'll go through it step-by-step. The first two lines in the following code define a date instance from the current time and a map using Groovy's map syntax that will dictate which properties you're going to query. The map's keys are the property names to query, and the values define the value to query by:

1 def today = new Date()
2 def queryMap =   [ genre: 'Alternative', dateCreated: [today-10,today] ]

Tip In the previous code example, to calculate the date range to be the past ten days, we took a java.util.Date instance and subtracted ten from it. This is an example of Groovy's operator overloading feature used to simplify date operations.


On line 3 a closure is assigned to the query variable, which will be used in conjunction with the criteria. The closure's closing bracket is on line 15, but some important stuff is going on in the body of the closure:

3 def query = {
     ...
15 }

First, a built-in GDK method called each is used to loop over each entry in the Map. Essentially, the method iterates through each element in the map and passes the key and value to the passed closure as arguments.

5    queryMap.each { key, value ->
               ...
14  }

Next up, the familiar instanceof operator is used to check whether the value passed is a List. If the value passed is a List, you can invoke the between method passing the key and the value. The value is split into two arguments using Groovy's * spread operator:

7                     if(value instanceof List) {
8                                  // use the spread operator to invoke
9                                  between(key, *value)
10                    }
11                    else {
12                                 like(key,value)
13                    }

The * spread operator's job is to split apart a List or an array and pass the separated values to the target. In this case, the between method, which actually takes three arguments, not two, is correctly called, with the first element of the list being the second argument and with the second element being the third argument.

Now let's start to look at how the query, in the form of a closure, works with a criteria instance as a reusable code block. As usual, of course, you have to create the criteria instance, which is accomplished on line 18:

18 def criteria = Album.createCriteria()

The various methods of the criteria instance are then utilized using the same closure:

21 println( criteria.count(query) )
24 println( criteria.get(query) )
27 criteria.list(query).each { println it }

The first, on line 21, counts all the results for the query; the next prints out a unique result (if there is one), and finally, the last lists all the results for the query and then iterates through them with the already encountered each method printing each one to standard out.

There is one more usage on line 30, which uses the scroll method on criteria. This returns an instance of the Hibernate class called org.hibernate.ScrollableResults, which has a similar interface to a JDBC java.sql.ResultSet and shares many of the same methods. One major difference, however, is that the columns of results are indexed from 0 and not 1 as in JDBC.

Querying Associations with Criteria

Often it is useful to execute a query that uses the state of an association as its criterion. So far, you have performed queries against only a single class and not its associations. So, how do you go about querying an association?

Grails' criteria builder allows querying associations by using a nested criteria method call whose name matches the property name. The closure argument passed to the method contains nested criteria calls that relate to the association and not the criteria class. For example, say you wanted to find all albums that contain the word Shake. The criteria shown in Listing 10-13 would do this.

Listing 10-13. Querying Associations with Criteria

def criteria = Album.withCriteria {        songs {             ilike('title', '%Shake%')        } }

This is a fairly trivial example, but all the criteria you've seen so far can be nested within the nested songs method call in the code listing. Combine this with how criteria can be built up from logical code blocks, and it results in a pretty powerful mechanism for querying associations.


Tip You can also combine association criteria as shown in Listing 10-13 with regular criteria on the class itself.


Querying with Projections

Projections allow the results of criteria queries to be customized in some way. For example, you may want to count only the number of results as opposed to retrieving each one. In other words, they are equivalent to SQL functions such as count, distinct, and sum.

With criteria queries, you can specify a projections method call that takes a closure and provides support for these types of queries. Instead of criteria, however, the method calls within it map to another Hibernate class named org.hibernate.criterion.Projections.

Let's adapt the example in Listing 10-14 by adding a projection that results in counting the distinct Album titles in the Alternative genre.

Listing 10-14. Querying with Projections

def criteria = Album.createCriteria()
def count = criteria.get {
         projections {
              countDistinct('name')
         }
         songs {
              eq('genre', 'Alternative')
         }
}

Query by Example

An alternative to criteria queries is to pass an instance of the class you're looking for to the find or findAll method. This is an interesting option when used in conjunction with Groovy's additional implicit constructor for JavaBeans, as shown in Listing 10-15.

Listing 10-15. Query by Example

def album = Album.find( new Album(title:'Odelay') )

As you can see from Listing 10-15, the find method uses the properties set on the passed Album instance to formulate a query. Querying by example is a little limiting, because you don't have access to some of the more advanced expressions such as Like, Between, and GreaterThan when passing in the example. It is, however, another useful addition to your toolbox.

HQL and SQL

Another way to perform queries is via the Hibernate Query Language (HQL), which is a flexible object-oriented alternative to SQL. A full discussion of HQL is beyond the scope of this book; however, the Hibernate documentation does cover it splendidly at http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html. We will look at some basic examples of how GORM supports HQL via more built-in methods.

Those who know SQL should not find it hard to adapt to HQL, because the syntactic differences are minimal. GORM provides three methods for working with HQL queries: find, findAll, and executeQuery. Each method, when passed a string, will assume it's an HQL query. The example in Listing 10-16 presents the most basic case combined with findAll.

Listing 10-16. HQL via the findAll Method

// query for all albums
def allAlbums = Album.findAll('from com.g2one.gtunes.Album')

In addition, JDBC-style IN parameters (queries with question mark [?] placeholders) are supported by passing a list as the second argument. Thanks to Groovy's concise syntax for expressing lists, the result is very readable, as presented in Listing 10-17.

Listing 10-17. HQL with Positional Parameters

// query for an Album by title
def album = Album.find(
                        'from Album as a where a.title = ?',
                        ['Odelay'])

If positional parameters aren't your preferred option, you can also use named parameters using the syntax shown in Listing 10-18.

Listing 10-18. HQL with Named Parameters

// query for an Album by title
def album = Album.find(
                      'from Album as a where a.title = :theTitle',
                      [theTitle:'Odelay'])

Notice how you use the colon character directly before the named parameter :theTitle. Then instead of passing a list as the final argument to the find method, you pass a map where the keys in the map match the named parameters in the query.

The methods find and findAll assume the query is a query specific to the Album class and will validate that this is so. It is possible, however, to execute other HQL queries via the executeQuery method, as shown in Listing 10-19.

Listing 10-19. HQL via executeQuery

// get all the songs
def songs = Album.executeQuery('select elements(b.songs) from Album as a')

Clearly, there is a lot to learn about HQL, since it is possible to perform more advanced queries using joins, aggregate functions, and subqueries. Luckily, the documentation on the Hibernate web site is an excellent overview of what is possible and can help you on your way.

Pagination

Whichever way you query, a typically useful thing to be able to do is paginate through a set of results. You've already learned that the list() method supports arguments such as max and offset that allow you to perform pagination. For example, to obtain the first ten results, you can use the following:

def results = Album.list(max:10)

To obtain the following ten, you can use the offset argument:

def results = Album.list(max:10, offset:10)

While we're on the topic of querying, you'll be happy to know that the same arguments can be used to paginate queries. For example, when using dynamic finders, you can pass a map as the last argument, which specifies the max and offset arguments:

def results = Album.findAllByGenre("Alternative", [max:10, offset:20])

In fact, you can use any parameter covered in the previous "Listing, Sorting, and Counting" section, such as sort and order:

def results = Album.findAllByGenre("Alternative", [sort:'dateCreated',
                                                   order:'desc'])

In the view, you can take advantage of the <g:paginate> tag that renders "Previous" and "Next" links as well as linked numbers to jump to a specific set of results à la Google. In its simplest form, the <g:paginate> tag requires only the total number of records:

<g:paginate total="${Album.count()}" />

This example assumes you want to paginate the current controller action. If this is not the case, you can customize the controller that is actually performing the pagination using the same attributes accepted by the <g:link> tag such as controller and action:

<g:paginate controller="album" action="list" total="${Album.count()}" />

You can change the default "Previous" and "Next" links using the prev and next attributes, respectively:

<g:paginate prev="Back" next="Forward" total="${Album.count()}" />

If internationalization (i18n) is a requirement, you can use the <g:message> tag, called as a method, to pull the text to appear from message bundles:

<g:paginate prev="${message(code:'back.button.text')}"
            next="${message(code:'next.button.text')}"
            total="${Album.count()}" />

Tip If you're interested in the mechanics of i18n support in Grails, take a look at Chapter 7, which covers the details of message bundles and switching locales.


Configuring GORM

GORM has a number of attributes that you may want to configure. Pretty much all the options available in Hibernate are also available in GORM. One of the most fundamental things you're likely to want to achieve is to enable some form of SQL logging so that you can debug performance issues and optimize queries.

SQL Logging

If you're purely interested in monitoring the amount of SQL traffic hitting the database, then a good option to use is the logSql setting in the grails-app/conf/DataSource.groovy file:

dataSource {
    ...
    logSql = true }

With this enabled, every SQL statement issued by Hibernate will be printed to the console. The disadvantage of the logSql setting is that you get to see only the prepared statements printed to the console and not the actual values that are being inserted. If you need to see the values, then set up a special log4j logger in grails-app/conf/Config.groovy as follows:

log4j = {
    ...
    logger {
        trace "org.hibernate.SQL",
              "org.hibernate.type"
    }
}

Specifying a Custom Dialect

Hibernate has, over the years, been heavily optimized for each individual database that it supports. To support different database types, Hibernate models the concept of a dialect. For each database it supports, there is a dialect class that knows how to communicate with that database.

There are even different dialects for different database versions. For example, for Oracle, there are three dialect classes: Oracle8iDialect, Oracle9iDialect, and Oracle10gDialect. Normally, the dialect to use is automatically detected from the database JDBC metadata. However, certain database drivers do not support JDBC metadata, in which case you may have to specify the dialect explicitly. To do so, you can use the dialect setting of the grails-app/conf/DataSource.groovy file. As an example, if you use the InnoDB storage engine for MySQL, you'll want to use the MySQL5InnoDBDialect class, as shown in Listing 10-20.

Listing 10-20. Customizing the Hibernate Dialect

dataSource {
    ...
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}

Other Hibernate Properties

The logSql and dialect settings of the DataSource.groovy file demonstrated in the previous two sections are actually just shortcuts for the hibernate.show_sql and hibernate.dialect properties of the Hibernate SessionFactory. If you're more comfortable using Hibernate's configuration model, then you can do so within a hibernate block in DataSource.groovy. In fact, you'll note that the Hibernate second-level cache (discussed in the "Caching" section later in the chapter) is already preconfigured in this manner, as shown in Listing 10-21.

Listing 10-21. Regular Hibernate Configuration

hibernate {
    cache.use_second_level_cache=true
    cache.use_query_cache=true
    cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}

You can configure all manner of things if you're not satisfied with the defaults set up by Grails. For example, to change your database's default transaction isolation level, you could use the hibernate.connection.isolation property:

hibernate {
    hibernate.connection.isolation=4
}

In this example, we've changed the isolation level to Connection. TRANSACTION_REPEATABLE_READ. Refer to the java.sql.Connection class for the other isolation levels.


Tip To find out all the other configuration options available, take a look at the Hibernate reference material on the subject at http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html.


The Semantics of GORM

As you have discovered so far, using GORM is pretty easy. It's so easy, in fact, that you may be lulled into a false sense of security thinking that you never have to look at a database again. However, when working with any ORM tool, it is absolutely critical to understand how and what the ORM tool is doing.


Tip Since GORM is built on Hibernate, it may well be worth investing in a book specifically targeting Hibernate; however, we'll do our best to cover the key aspects here.


If you start using an ORM tool without understanding its semantics, you will almost certainly run into issues with the performance and behavior of your application. ORM tools are often referred to as an example of a leaky abstraction (see http://www.joelonsoftware.com/articles/LeakyAbstractions.html) because they attempt to isolate you from the complexities of the underlying database. Unfortunately, to follow the analogy, the abstraction leaks quite frequently if you're not aware of features such as lazy and eager fetching, locking strategies, and caching.

This chapter will provide some clarity on these quirks and ensure that you don't use GORM with the expectation that it will solve world hunger. GORM is often compared, understandably, to ActiveRecord in Rails. Unfortunately, users with Rails experience who adopt Grails are in for a few surprises because the tools are really quite different. One of the primary differences is that GORM has the concept of a persistence context, or session.

The Hibernate Session

Hibernate, like the Java Persistence API, models the concept of a persistence session using the org.hibernate.Session class. The Session class is essentially a container that holds references to all known instances of persistent classes—domain classes in Grails. In the Hibernate view of the world, you think in terms of objects and delegate responsibility to Hibernate to ensure that the state of the objects is synchronized to the database.

The synchronization process is triggered by calling the flush() method on the Session object. At this point, you may be wondering how all of this relates to Grails, given that you saw no mention of a Session object in Chapter 4. Essentially, GORM manipulates the Session object transparently on your behalf.

It is quite possible to build an entire Grails application without ever interacting directly with the Hibernate Session object. However, for developers who are not used to the session model, there may be a few surprises along the way. As an example, consider the code in Listing 10-22.

Listing 10-22. Multiple Reads Return the Same Object

def album1 = Album.get(1)
def album2 = Album.get(1)
assertFalse album1.is(album2)

The code in Listing 10-1 shows a little gotcha for developers not used to the session model. The first call to the get method retrieves an instance of the Album class by executing a SQL SELECT statement under the covers—no surprises there. However, the second call to the get method doesn't execute any SQL at all, and in fact, the assertion on the last lines fails.


Note In the example in Listing 10-22, the final assertFalse statement uses Groovy's is method because == in Groovy is equivalent to calling the equals(Object) method in Java.


In other words, the Session object appears to act like a cache, and in fact it is one. The Session object is Hibernate's first-level cache. Another area where this is apparent is when saving an object. For example, consider the code in Listing 10-23.

Listing 10-23. Saving a Domain Class in Grails

def album = new Album(..)
album.save()

Now, assuming the Album instance validates, you may think from the code in Listing 10-2 that GORM will execute a SQL INSERT statement when the save() method is called. However, this is not necessarily the case, and in fact it depends greatly on the underlying database. GORM by default uses Hibernate's native identity generation strategy, which attempts to select the most appropriate way to generate the id of an object. For example, in Oracle, Hibernate will opt to use a sequence generator to supply the identifier, while in MySQL the identity strategy will be used. The identity generation strategy relies on the database to supply the identity.

Since an identifier must be assigned by the time the save() method completes, if a sequence is used, no INSERT is needed because Hibernate can simply increment the sequence in memory. The actual INSERT can then occur later when the Session is flushed. However, in the case of the identity strategy, an INSERT is needed since the database needs to generate the identifier. Nevertheless, the example serves to demonstrate that it is the Session that is responsible for synchronizing the object's state to the database, not the object itself.

Essentially, Hibernate implements the strategy known as transactional write-behind. Any changes you make to persistent objects are not necessarily persisted when you make them or even when you call the save() method. The advantage of this approach is that Hibernate can heavily optimize and batch up the SQL to be executed, hence minimizing network traffic. In addition, the time for which database locks (discussed in more detail in the "Locking Strategies" section) are held is greatly reduced by this model.

Session Management and Flushing

You may be worried at this point that you're losing some kind of control by allowing Hibernate to take responsibility for persisting objects on your behalf. Fortunately, GORM provides you with the ability to control session flushing implicitly by passing in a flush argument to the save() or delete() method, as shown in Listing 10-24.

Listing 10-24. Manually Flushing the Session

def album = new Album(..)
album.save(flush:true)

In contrast to the example in Listing 10-23, the code in Listing 10-24 will persist the object but also call flush() on the underlying Session object. However, it is important to note that since the Session deals with all persistent instances, other changes may be flushed in addition to the object that is saved. Listing 10-25 illustrates an example of this behavior.

Listing 10-25. The Effects of Flushing

def album1 = Album.get(1)
album1.title = "The Changed Title"
album1.save()
def album2 = new Album(..)
album2.save(flush:true)

The example in Listing 10-25 demonstrates the impact of passing the flush argument to the second save() method. You may expect that a SQL UPDATE would be executed when save() is called on album1, and then an INSERT would occur when save() is called on album2. However, the actual behavior is that both the UPDATE and the INSERT occur on the call to save() on album2, since the flush:true argument passed forces the underlying Session object to synchronize changes with the database.

You may be wondering at this point how the code in the listings you've seen so far can possibly use the same Session instance and where this Session came from in the first place. Basically, when a request comes into a Grails application, directly before a controller action executes, Grails will transparently bind a new Hibernate Session to the current thread. The Session is then looked up by GORM's dynamic methods like get in Listing 10-8. When a controller action finishes executing, if no exceptions are thrown, the Session is flushed, which synchronizes the state of the Session with the database by executing any necessary SQL. These changes are then committed to the database.

However, that is not the end of the story. The Session is not closed but instead placed in read-only mode prior to view rendering and remains open until view rendering completes. The reason for this is that if the session were closed, any persistent instances contained within it would become detached. The result is that if there were any noninitialized associations, the infamous org.hibernate.LazyInitializationException would occur. Ouch! Of course, we'll be saying more about LazyInitializationException and ways to avoid the exception, including in-depth coverage of detached objects later in the chapter.

To elaborate, the reason for placing the Session into read-only mode during view rendering is to avoid any unnecessary flushing of the Session during the view-rendering process. Your views really shouldn't be modifying database state after all! So, that is how the standard Session life cycle works in Grails. However, there is an exception. In the previous chapter, you explored Web Flow, which allows you to construct rich conversations that model multistep processes. Unlike regular requests, the Session is not scoped to the request but instead to the entire flow.

When the flow is started, a new Session is opened and bound to flow scope. Then whenever the flow resumes execution, the same session is retrieved. In this case, all the GORM methods work with the session bound into flow scope. Finally, when the flow terminates at an end state, the Session is flushed, and any changes are committed to the database.

Obtaining the Session

Now, as mentioned previously, the Session is basically a cache of persistent instances. Like any cache, the more objects it has within it, the more memory it's going to consume. A common mistake when using GORM is to query for a large number of objects without periodically clearing the Session. If you do so, your Session will get bigger and bigger, and eventually you may either cause your application's performance to suffer or, worse, run out of memory.

In these kinds of scenarios, it is wise to manage the state of your Session manually. Before you can do so, however, you need a reference to the Session object itself. You can achieve this in two ways. The first involves the use of dependency injection to get hold of a reference to the Hibernate SessionFactory object.

The SessionFactory has a method called currentSession() that you can use to obtain the Session bound to the current thread. To use dependency injection, simply declare a local field called sessionFactory in a controller, tag library, or service, as shown in Listing 10-26.

Listing 10-26. Using Dependency Injection to Obtain the Hibernate Session

def sessionFactory
...
def index = {
    def session = sessionFactory.currentSession()
}

As an alternative, you could use the withSession method that is available on any domain class. The withSession method accepts a closure. The first argument to the closure is the Session object; hence, you can code as in Listing 10-27.

Listing 10-27. Using the withSession Method

def index = {
    Album.withSession { session ->
       ...
    }
}

Let's return to the problem at hand. To avoid memory issues when using GORM with a large amount of data (note this applies to raw Hibernate too), you need to call the clear() method on the Session object periodically so that the contents of the Session are cleared. The result is that the instances within the Session become candidates for garbage collection, which frees up memory. Listing 10-28 shows an example that demonstrates the pattern.

Listing 10-28. Managing the Hibernate Session

1 def index = {
2    Album.withSession { session ->
3        def allAlbums = Album.list()
4        for(album in allAlbums) {
5            def songs = Song.findAllByAlbum(album)
6            // do something with the songs
7            ...
8            session.clear()
9        }
10    }
11 }

The example in Listing 10-28 is rather contrived, but it serves to demonstrate effective Session management when dealing with a large number of objects. On line 2, a reference to the Session is obtained using the withSession method:

2    Album.withSession { session ->
        ...
10    }

Then, on line 3, a query is used to get a list of all the albums in the system, which could be big in itself, and then iterate over each one:

3      def allAlbums = Album.list()
4      for(album in allAlbums) {
          ..
9      }

Critically, on line 5, a dynamic finder queries for all the Song instances for the current Album:

5        def songs = Song.findAllByAlbum(album)

Now, each time the findAllByAlbum method is executed, more and more persistent instances are being accumulated in the Session. Memory consumption may at some point become an issue depending on how much data is in the system at the time. To prevent this, the session is cleared on line 8:

8        session.clear()

Clearing the Session with the clear() method is not the only way to remove objects from Hibernate's grasp. If you have a single object, you can also call the discard() method. You could even use the *. operator to discard entire collections of objects using this technique:

songs*.discard()

The advantage of this approach is that although the clear() method removes all persistent instances from the Session, using discard() removes only the instances you no longer need. This can help in certain circumstances because you may end up with a LazyInitializationException because removing the objects from the Session results in them being detached (a subject we'll discuss in more detail in the "Detached Objects" section).

Automatic Session Flushing

Another common gotcha is that by default GORM is configured to flush the session automatically when one of the following occurs:

  • Whenever a query is run
  • Directly after a controller action completes, if no exceptions are thrown
  • Directly before a transaction is committed

This has a number of implications that you need to consider. Take, for example, the code in Listing 10-29.

Listing 10-29. The Implications of Automatic Session Flushing

1     def album = Album.get(1)
2     album.title = "Change It"
3     def otherAlbums = Album.findAllWhereTitleLike("%Change%")
4
5     assert otherAlbums.contains(album)

Now, you may think that because you never called save() on the album there is no way it could possibly have been persisted to the database, right? Wrong. As soon as you load the album instance, it immediately becomes a "managed" object as far as Hibernate is concerned. Since Hibernate is by default configured to flush the session when a query runs, the Session is flushed on line 3 when the findAllWhereTitleLike method is called and the Album instance is persisted. The Hibernate Session caches changes and pushes them to the database only at the latest possible moment. In the case of automatic flushing, this is at the end of a transaction or before a query runs that might be affected by the cached changes.

You may consider the behavior of automatic flushing to be a little odd, but if you think about it, it depends very much on your expectations. If the object weren't flushed to the database, then the change made to it on line 2 would not be reflected in the results. That may not be what you're expecting either! Let's consider another example where automatic flushing may present a few surprises. Take a look at the code in Listing 10-30.

Listing 10-30. Another Implication of Automatic Session Flushing

def album = Album.get(1)
album.title = "Change It"

In Listing 10-16, an instance of the Album class is looked up and the title is changed, but the save() method is never called. You may expect that since save() was never called, the Album instance will not be persisted to the database. However, you'd be wrong again. Hibernate does automatic dirty checking and flushes any changes to the persistent instances contained within the Session.

This may be what you were expecting in the first place. However, one thing to consider is that if you simply allow this to happen, then Grails' built-in validation support, discussed in Chapter 3, will not kick in, resulting in a potentially invalid object being saved to the database.

It is our recommendation that you should always call the save() method when persisting objects. The save() method will call Grails' validation mechanism and mark the object as read-only, including any associations of the object, if a validation error occurs. If you were never planning to save the object in the first place, then you may want to consider using the read method instead of the get method, which returns the object in a read-only state:

def album = Album.read(1)

If all of this is too dreadful to contemplate and you prefer to have full control over how and when the Session is flushed, then you may want to consider changing the default FlushMode used by specifying the hibernate.flush.mode setting in DataSource.groovy:

hibernate.flush.mode="manual"

The possible values of the hibernate.flush.mode setting are summarized as follows:

  • manual: Flush only when you say so! In other words, only flush the session when the flush:true argument is passed to save() or delete(). The downside with a manual flush mode is that you may receive stale data from queries, and you must always pass the flush:true argument to the save() or delete() method.
  • commit: Flush only when the transaction is committed (see the next section).
  • auto: Flush when the transaction is committed and before a query is run.

Nevertheless, assuming you stick with the default auto setting, the save() method might not, excuse the pun, save you in the case of the code from Listing 10-15. Remember in this case the Session is automatically flushed before the query is run. This problem brings us nicely onto the topic of transactions in GORM.

Transactions in GORM

First things first—it is important to emphasize that all communication between Hibernate and the database runs within the context of a database transaction regardless of whether you are explicit about the transaction demarcation boundaries. The Session itself is lazy in that it only ever initiates a database transaction at the last possible moment.

Consider the code in Listing 10-15 again. When the code is run, a Session has already been opened and bound to the current thread. However, a transaction is initiated only on first communication with the database, which happens within the call to get on line 1.

At this point, the Session is associated with a JDBC Connection object. The autoCommit property of the Connection object is set to false, which initiates a transaction. The Connection will then be released only once the Session is closed. Hence, as you can see, there is never really a circumstance where Grails operates without an active transaction, since the same Session is shared across the entire request.

Given that there is a transaction anyway, you would think that if something went wrong, any problems would be rolled back. However, without specific transaction boundaries and if the Session is flushed, any changes are permanently committed to the database.

This is a particular problem if the flush is beyond your control (for instance, the result of a query). Then those changes will be permanently persisted to the database. The result may be the rather painful one of having your database left in an inconsistent state. To help you understand, let's look at another illustrative example, as shown in Listing 10-31.

Listing 10-31. Updates Gone Wrong

def save = {
    def album = Album.get(params.id)
    album.title = "Changed Title"
    album.save(flush:true)
    ...
    // something goes wrong
    throw new Exception("Oh, sugar.")
}

The example in Listing 10-15 shows a common problem. In the first three lines of the save action, an instance of the Album class is obtained using the get method, the title is updated, and the save() method is called and passes the flush argument to ensure updates are synchronized with the database. Then later in the code, something goes wrong, and an exception is thrown. Unfortunately, if you were expecting previous updates to the Album instance to be rolled back, you're out of luck. The changes have already been persisted when the Session was flushed! You can correct this in two ways; the first is to move the logic into a transactional service. Services are the subject of Chapter 11, so we'll be showing the latter option, which is to use programmatic transactions. Listing 10-32 shows the code updated to use the withTransaction method to demarcate the transactional boundaries.

Listing 10-32. Using the withTransaction Method

def save = {
    Album.withTransaction {
        def album = Album.get(params.id)
        album.title = "Changed Title"
        album.save(flush:true)
        ...
        // something goes wrong
        throw new Exception("Oh, sugar.")
    }
}

Grails uses Spring's PlatformTransactionManager abstraction layer under the covers. In this case, if an exception is thrown, all changes made within the scope of the transaction will be rolled back as expected. The first argument to the withTransaction method is a Spring TransactionStatus object, which also allows you to programmatically roll back the transaction by calling the setRollbackOnly() method, as shown in Listing 10-33.

Listing 10-33. Programmatically Rolling Back a Transaction

def save = {
     Album.withTransaction { status ->
         def album = Album.get(params.id)
         album.title = "Changed Title"
         album.save(flush:true)
         ...
         // something goes wrong
         if(hasSomethingGoneWrong()) {
             status.setRollbackOnly()
         }
    }
}

Note that you need only one withTransaction declaration. If you were to nest withTransaction declarations within each other, then the same transaction would simply be propagated from one withTransaction block to the next. The same is true of transactional services. In addition, if you have a JDBC 3.0–compliant database, then you can leverage save-points, which allow you to roll back to a particular point rather than rolling back the entire transaction. Listing 10-34 shows an example that rolls back any changes made after the Album instance was saved.

Listing 10-34. Using Savepoints in Grails

def save = {
    Album.withTransaction { status ->
        def album = Album.get(params.id)
        album.title = "Changed Title"
        album.save(flush:true)
        def savepoint = status.createSavepoint()
        ...
        // something goes wrong
        if(hasSomethingGoneWrong()) {
            status.rollbackToSavepoint(savepoint)
            // do something else
            ...
         }
    }
}

With transactions out of the way, let's revisit a topic that has been touched on at various points throughout this chapter: detached objects.

Detached Objects

The Hibernate Session is critically important to understand the nature of detached objects. Remember, the Session keeps track of all persistent instances and acts like a cache, returning instances that already exist in the Session rather than hitting the database again. As you can imagine, each object goes through an implicit life cycle, a topic we'll be looking at first.

The Persistence Life Cycle

Before an object has been saved, it is said to be transient. Transient objects are just like regular Java objects and have no notion of persistence. Once you call the save() method, the object is in a persistent state. Persistent objects have an assigned identifier and may have enhanced capabilities such as the ability to lazily load associations. If the object is discarded by calling the discard() method or if the Session has been cleared, it is said to be in a detached state. In other words, each persistent object is associated with a single Session, and if the object is no longer managed by the Session, it has been detached from the Session.

Figure 10-2 shows a state diagram describing the persistence life cycle and the various states an object can go through. As the diagram notes, another way an object can become detached is if the Session itself is closed. If you recall, we mentioned that a new Session is bound for each Grails request. When the request completes, the Session is closed. Any objects that are still around, for example, held within the HttpSession, are now in a detached state.

image

Figure 10-2. The persistence life cycle

So, what is the implication of being in a detached state? For one, if a detached object that is stored in the HttpSession has any noninitialized associations, then you will get a LazyInitializationException.

Reattaching Detached Objects

Given that it is probably undesirable to experience a LazyInitializationException, you can eliminate this problem by reassociating a detached object with the Session bound to the current thread by calling the attach() method, for example:

album.attach()

Note that if an object already exists in the Session with the same identifier, then you'll get an org.hibernate.NonUniqueObjectException. To get around this, you may want to check whether the object is already attached by using the isAttached() method:

if(!album.isAttached()) {
    album.attach()
}

Since we're on the subject of equality and detached objects, it's important to bring up the notion of object equality here. If you decide you want to use detached objects extensively, then it is almost certain that you will need to consider implementing equals and hashCode for all of your domain classes that are detached. Why? Well, if you consider the code in Listing 10-35, you'll soon see why.

Listing 10-35. Object Equality and Hibernate

def album1 = Album.get(1)
album.discard()

def album2 = Album.get(1)

assert album1 == album2 // This assertion will fail

The default implementation of equals and hashCode in Java uses object equality to compare instances. The problem is that when an instance becomes detached, Hibernate loses all knowledge of it. As the code in Listing 10-35 demonstrates, loading two instances with the same identifier once one has become detached results in you having two different instances. This can cause problems when placing these objects into collections. Remember, a Set uses hashCode to work out whether an object is a duplicate, but the two Album instances will return two different hash codes even though they share the same database identifier!

To get around this problem, you could use the database identifier, but this is not recommended, because a transient object that then becomes persistent will return different hash codes over time. This breaks the contract defined by the hashCode method, which states that the hashCode implementation must return the same integer for the lifetime of the object. The recommended approach is to use the business key, which is typically some logical property or set of properties that is unique to and required by each instance. For example, with the Album class, it may be the Artist name and title. Listing 10-36 shows an example implementation.

Listing 10-36. Implementing equals and hashCode

class Album {
    ...
    boolean equals(o) {
        if(this.is(o)) return true
        if( !(o instanceof Album) ) return false
        return this.title = o.title && this.artist?.name = o.artist?.name
    }
    int hashCode() {
        this.title.hashCode() + this.artist?.name?.hashCode() ?: 0
    }
}

An important thing to remember is that you need to implement equals and hashCode only if you are:

  • Using detached instances extensively
  • Placing the detached instances into data structures, like the Set and Map collection types, that use hashing algorithms to establish equality

The subject of equality brings us nicely onto another potential stumbling block. Say you have a detached Album instance held somewhere like in the HttpSession and you also have another Album instance that is logically equal (they share the same identifier) to the instance in the HttpSession. What do you do? Well, you could just discard the instance in the HttpSession:

def index = {
    def album = session.album
    if(album.isAttached()) {
        album = Album.get(album.id)
        session.album = album
    }
}

However, what if the detached album in the HttpSession has changes? What if it represents the most up-to-date copy and not the one already loaded by Hibernate? In this case, you need to consider merging.

Merging Changes

To merge the state of one, potentially detached, object into another, you need to use the static merge method. The merge method accepts an instance, loads a persistent instance of the same logical object if it doesn't already exist in the Session, and then merges the state of the passed instance into the loaded persistent one. Once this is done, the merge method then returns a new instance containing the merged state. Listing 10-37 presents an example of using the merge method.

Listing 10-37. Using the merge Method

def index = {
    def album = session.album
    album = Album.merge(album)
    render album.title
}

Performance Tuning GORM

The previous section on the semantics of GORM showed how the underlying Hibernate engine optimizes database access using a cache (the Session). There are, however, various ways to optimize the performance of your queries. In the next few sections, we'll be covering the different ways to tune GORM, allowing you to get the best out of the technology. You may want to enable SQL logging by setting logSql to true in DataSource.groovy, as explained in the previous section on configuring GORM.

Eager vs. Lazy Associations

Associations in GORM are lazy by default. What does this mean? Well, say you looked up a load of Album instances using the static list() method:

def albums = Album.list()

To obtain all the Album instances, underneath the surface Hibernate will execute a single SQL SELECT statement to obtain the underlying rows. As you already know, each Album has an Artist that is accessible via the artist association. Now say you need to iterate over each song and print the Artist name, as shown in Listing 10-38.

Listing 10-38. Iterating Over Lazy Associations

def albums = Album.list()
for(album in albums) {
    println album.artist.name
}

The example in Listing 10-38 demonstrates what is commonly known as the N+1 problem. Since the artist association is lazy, Hibernate will execute another SQL SELECT statement (N statements) for each associated artist to add to the single statement to retrieve the original list of albums. Clearly, if the result set returned from the Album association is large, you have a big problem. Each SQL statement executed results in interprocess communication, which drags down the performance of your application. Listing 10-39 shows the typical output you would get from the Hibernate SQL logging, shortened for brevity.

Listing 10-39. Hibernate SQL Logging Output Using Lazy Associations

Hibernate:
    select
        this_.id as id0_0_,
        this_.version as version0_0_,
        this_.artist_id as artist3_0_0_,
        ...
    from
        album this_
Hibernate:
    select
        artist0_.id as id8_0_,
        ...
    from
        artist artist0_
    where
        artist0_.id=?
Hibernate:
    select
        artist0_.id as id8_0_,
        ...
    from
        artist artist0_
    where
        artist0_.id=?
...

A knee-jerk reaction to this problem would be to make every association eager. An eager association uses a SQL JOIN so that all Artist associations are populated whenever you query for Album instances. Listing 10-40 shows you can use the mapping property to configure an association as eager by default.

Listing 10-40. Configuring an Eager Association

class Album {
    ...
    static mapping = {
        artist fetch:'join'
    }
}

However, this may not be optimal either, because you may well run into a situation where you pull your entire database into memory! Lazy associations are definitely the most sensible default here. If you're merely after the identifier of each associated artist, then it is possible to retrieve the identifier without needing to do an additional SELECT. All you need to do is refer to the association name plus the suffix Id:

def albums = Album.list()
for(album in albums) {
    println album.artistId // get the artist id
}

However, as the example in Listing 10-38 demonstrates, there are certain examples where a join query is desirable. You could modify the code as shown in Listing 10-41 to use the fetch argument.

Listing 10-41. Using the fetch Argument to Obtain Results Eagerly

def albums = Album.list(fetch:[artist:'join'])
for(album in albums) {
    println album.artist.name
}

If you run the code in Listing 10-41, instead of N+1 SELECT statements, you get a single SELECT that uses a SQL INNER JOIN to obtain the data for all artists too. Listing 10-42 shows the output from the Hibernate SQL logging for this query.

Listing 10-42. Hibernate SQL Logging Output Using Eager Association


select
    this_.id as id0_1_,
    this_.version as version0_1_,
    this_.artist_id as artist3_0_1_,
    this_.date_created as date4_0_1_,
    this_.genre as genre0_1_,
    this_.last_updated as last6_0_1_,
    this_.price as price0_1_,
    this_.title as title0_1_,
    this_.year as year0_1_,
    artist2_.id as id8_0_,
    artist2_.version as version8_0_,
    artist2_.date_created as date3_8_0_,
    artist2_.last_updated as last4_8_0_,
    artist2_.name as name8_0_
from
    album this_
inner join
    artist artist2_
        on this_.artist_id=artist2_.id

Of course, the static list() method is not the only case where you require a join query to optimize performance. Luckily, dynamic finders, criteria, and HQL can all be used to perform a join. Using a dynamic finder, you can use the fetch parameter by passing a map as the last argument:

def albums = Album.findAllByGenre("Alternative", [fetch:[artist:'join']])

Using criteria queries you can use the join method:

def albums = Album.withCriteria {
    ...
    join 'artist'
}

And, finally, with HQL you can use a similar syntax to SQL by specifying the inner join in the query:

def albums = Album.findAll("from Album as a inner join a.artist as artist")

Batch Fetching

As you discovered in the previous section, using join queries can solve the N+1 problem by reducing multiple SQL SELECT statements to a single SELECT statement that uses a SQL JOIN. However, join queries too can be expensive, depending on the number of joins and the amount of data being pulled from the database.

As an alternative, you could use batch fetching, which serves as an optimization of the lazy fetching strategy. With batch fetching, instead of pulling in a single result, Hibernate will use a SELECT statement that pulls in a configured number of results. To take advantage of batch fetching, you need to set the batchSize at the class or association level.

As an example, say you had a long Album with 23 songs. Hibernate would execute a single SELECT to get the Album and then 23 extra SELECT statements for each Song. However, if you configured a batchSize of 10 for the Song class, Hibernate would perform only 3 queries in batches of 10, 10, and 3. Listing 10-43 shows how to configure the batchSize using the mapping block of the Song class.

Listing 10-43. Configuring the batchSize at the Class Level

class Song {
    ...
    static mapping = {
        batchSize 10
    }
}

Alternatively, you can also configure the batchSize on the association. For example, say you loaded 15 Album instances. Hibernate will execute a SELECT every time the songs association of each Album is accessed, resulting in 15 SELECT statements. If you configured a batchSize of 5 on the songs association, you would only get 3 queries. Listing 10-44 shows how to configure the batchSize of the songs association.

Listing 10-44. Configuring the batchSize of an Association

class Album {
    ...
    static mapping = {
        songs batchSize:10
    }
}

As you can see from this discussion on eager vs. lazy fetching, a large part of optimizing an application's performance lies in reducing the number of calls to the database. Eager fetching is one way to achieve that, but you're still making a trip to the database even if it's only one.

An even better solution is to eliminate the majority of calls to the database by caching the results. In the next section, we'll be looking at different caching techniques you can take advantage of in GORM.

Caching

In the previous "The Semantics of GORM" section, you discovered that the underlying Hibernate engine models the concept of a Session. The Session is also known as the first-level cache, because it stores the loaded persistent entities and prevents repeated access to the database for the same object. However, Hibernate also has a number of other caches including the second-level cache and the query cache. In the next section, we'll explain what the second-level cache is and show how it can be used to reduce the chattiness between your application and the database.

The Second-Level Cache

As discussed, as soon as a Hibernate Session is obtained by GORM, you already have an active cache: the first-level cache. Although the first-level cache stores actual persistent instances for the scope of the Session, the second-level cache exists for the whole time that the SessionFactory exists. Remember, the SessionFactory is the object that constructs each Session.

In other words, although a Session is typically scoped for each request, the second-level cache is application scoped. Additionally, the second-level cache stores only the property values and/or foreign keys rather than the persistent instances themselves. As an example, Listing 10-45 shows the conceptual representations of the Album class in the second-level cache.

Listing 10-45. How the Second-Level Cache Stores Data

9 -> ["Odelay",1994, "Alternative", 9.99, [34,35,36], 4]
5 -> ["Aha Shake Heartbreak",2004, "Rock", 7.99, [22,23,24], 8]

As you can see, the second-level cache stores the data using a map containing multidimensional arrays that represent the data. The reason for doing this is that Hibernate doesn't have to require your classes to implement Serializable or some persistence interface. By storing only the identifiers of associations, it eliminates the chance of the associations becoming stale. The previous explanation is a bit of an oversimplification; however, you don't need to concern yourself too much with the detail. Your main job is to specify a cache provider.

By default, Grails comes preconfigured with OSCache as the cache provider. However, Grails also ships with Ehcache, which is recommended for production environments. You can change the cache configuration in DataSource.groovy by modifying the settings shown in Listing 10-46.

Listing 10-46. Specifying a Cache Provider

hibernate {
    cache.use_second_level_cache=true
    cache.use_query_cache=true
    cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}

You can even configure a distributed cache such as Oracle Coherence or Terracotta, but be careful if your application is dependent on data not being stale. Remember, cached results don't necessarily reflect the current state of the data in the database.

Once you have a cache provider configured, you're ready to go. However, by default all persistent classes have no caching enabled. You have to be very explicit about specifying what data you want cached and what the cache policy is for that data.

There are essentially four cache policies available depending on your needs:

  • read-only: If your application never needs to modify data after it is created, then use this policy. It is also an effective way to enforce read-only semantics for your objects because Hibernate will throw an exception if you try to modify an instance in a read-only cache. Additionally, this policy is safe even when used in a distributed cache because there is no chance of stale data.
  • nonstrict-read-write: If your application rarely modifies data and transactional updates aren't an issue, then a nonstrict-read-write cache may be appropriate. This strategy doesn't guarantee that two transactions won't simultaneously modify a persistent instance. It is mainly recommended for usage in scenarios with frequent reads and only occasional updates.
  • read-write: If your application requires users to frequently modify data, then you may want to use a read-write cache. Whenever an object is updated, Hibernate will automatically evict the cached data from the second-level cache. However, there is still a chance of phantom reads (stale data) with this policy, and if transactional behavior is a requirement, you should not use a transactional cache.
  • transactional: A transactional cache provides fully transactional behavior with no chance of dirty reads. However, you need to make sure you supply a cache provider that supports this feature such as JBoss TreeCache.

So, how do you use these different cache levels in a Grails application? Essentially, you need to mark each class and/or association you want to cache using the cache method of the mapping block. For example, Listing 10-47 shows how to configure the default read-write cache for the Album class and a read-only cache for the songs association.

Listing 10-47. Specifying a Cache Policy

class Album {
    ...
    static mapping {
        cache true
        songs cache:'read-only''
    }
}

Now, whenever you query for results, before loading them from the database Hibernate will check whether the record is already present in the second-level cache and, if it is, load it from there. Now let's look at another one of Hibernate's caches: the query cache.

Query Caching

Hibernate, and hence GORM, supports the ability to cache the results of a query. As you can imagine, this is useful only if you have a frequently executed query that uses the same parameters each time. The query cache can be enabled and disabled using the hibernate.cache. use_query_cache setting in DataSource.groovy, as shown in Listing 10-46.


Note The query cache works together with the second-level cache, so unless you specify a caching policy as shown in the previous section, the results of a cached query will not be cached.


By default, not all queries are cached. Like caching of instances, you have to specify explicitly that a query needs caching. To do so, in the list() method you could use the cache argument:

def albums = Album.list(cache:true)

The same technique can be used with dynamic finders using a map passed as the last argument:

def albums = Album.findAllByGenre("Alternative", [cache:true])

You can also cache criteria queries using the cache method:

def albums = Album.withCriteria {
    ...
    cache true }

That's it for caching; in the next section, we'll cover the impacts of inheritance in ORM mapping.

Inheritance Strategies

As demonstrated in Chapter 3, you can implement inheritance using two different strategies called table-per-hierarchy or table-per-subclass. With a table-per-hierarchy mapping, one table is shared between the parent and all child classes, while table-per-subclass uses a different table for each subsequent subclass.

If you were going to identify one area of ORM technology that really demonstrates the object vs. relational mismatch, it would be inheritance mapping. If you go for table-per-hierarchy, then you're forced to have not-null constraints on all child columns because they share the same table. The alternative solution, table-per-subclass, could be seen as better since you avoid the need to specify nullable columns as each subclass resides in its own table.

The main disadvantage of table-per-subclass is that in a deep inheritance hierarchy you may end up with an excessive number of JOIN queries to obtain the results from all the parents of a given child. As you can imagine, this can lead to a performance problem if not used with caution; that's why we're covering the topic here.

Our advice is to keep things simple and try to avoid modeling domains with more than three levels of inheritance when using table-per-subclass. Alternatively, if you're happy sticking with table-per-hierarchy, then you're even better off because no JOIN queries at all are required. And with that, we end our coverage of performance tuning GORM. In the next section, we'll be covering locking strategies and concurrency.

Locking Strategies

Given that Grails executes within the context of a multithreaded servlet container, concurrency is an issue that you need to consider whenever persisting domain instances. By default, GORM uses optimistic locking with versioning. What this means is that the Hibernate engine does not hold any locks on database rows by performing a SELECT FOR...UPDATE. Instead, Hibernate versions every domain instance.

You may already have noticed that every table generated by GORM contains a version column. Whenever a domain instance is saved, the version number contained within the version column is incremented. Just before any update to a persistent instance, Hibernate will issue a SQL SELECT to check the current version. If the version number in the table doesn't match the version number of the instance being saved, then an org.hibernate. StaleObjectStateException is thrown, which is wrapped in a Spring org.springframework. dao.OptimisticLockingFailureException and rethrown.

The implication is that if your application is processing updates with a high level of concurrency, you may need to deal with the case when you get a conflicting version. The upside is that since table rows are never locked, performance is much better. So, how do you go about gracefully handling an OptimisticLockingFailureException? Well, this is a domain-specific question. You could, for example, use the merge method to merge the changes back into the database. Alternatively, you could return the error to the user and ask him to perform a manual merge of the changes. It really does depend on the application. Nevertheless, Listing 10-48 shows how to handle an OptimisticLockingFailureException using the merge technique.

Listing 10-48. Dealing with Optimistic Locking Exceptions

def update = {
    def album = Album.get(params.id)
    album.properties = params
    try {
        if(album.save(flush:true)) {
            // success
            ...
        }
        else {
            // validation error
            ...
        }
    }
    catch(OptimisticLockingFailureException e) {
        album = Album.merge(album)
        ...
   }
}

If you prefer not to use optimistic locking, either because you're mapping to a legacy database or because you just don't like it, then you can disable optimistic locking using the version method inside the mapping closure of a domain class:

static mapping = {
        version false
}

If you're not expecting a heavy load on your site, then an alternative may be to use pessimistic locking. Unlike optimistic locking, pessimistic locking will perform SELECT FOR...UPDATE on the underlying table row, which will block any other threads' access to the same row until the update is committed. As you can imagine, this will have an impact on the performance of your application. To use pessimistic locking, you need to call the static lock() method, passing the identifier of the instance to obtain a lock. Listing 10-49 shows an example of using pessimistic locking with the lock method.

Listing 10-49. Using the lock Method to Obtain a Pessimistic Lock

def update = {
    def album = Album.lock(params.id)
    ...
}

If you have a reference to an existing persistent instance, then you can call the lock() instance method, which upgrades to a pessimistic lock. Listing 10-50 shows how to use the lock instance method.

Listing 10-50. Using the lock Instance Method to Upgrade to a Pessimistic Lock

def update = {
    def album = Album.get(params.id)
    album.lock() // lock the instance
    ...
}

Note that you need to be careful when using the lock instance method because you still get an OptimisticLockingFailureException if another thread has updated the row in the time it takes to get the instance and call lock() on it! With locks out of the way, let's move on to looking at GORM's support for events.

Events Auto Time Stamping

GORM has a number of built-in hooks you can take advantage of to hook into persistence events. Each event is defined as a closure property in the domain class itself. The events available are as follows:

  • onLoad/beforeLoad: Fired when an object is loaded from the database
  • beforeInsert: Fired before an object is initially persisted to the database
  • beforeUpdate: Fired before an object is updated in the database
  • beforeDelete: Fired before an object is deleted from the database
  • afterInsert: Fired after an object has been persisted to the database
  • afterUpdate: Fired after an object has been updated in the database
  • afterDelete: Fired after an object has been deleted from the database

These events are useful for performing tasks such as audit logging and tracking.


Tip If you're interested in a more complete solution for audit logging, you may want to check out the Audit Logging plugin for Grails at http://www.grails.org/Grails+Audit+Logging+Plugin.


For example, you could have another domain class that models an AuditLogEvent that gets persisted every time an instance gets accessed or saved. Listing 10-51 shows this concept in action.

Listing 10-51. Using GORM Events

class Album {
    ...
    transient onLoad = {
        new AuditLogEvent(type:"read", data:title).save()
    }
    transient beforeSave = {
        new AuditLogEvent(type:"save", data:title).save()
    }
}

GORM also supports automatic time stamps. Essentially, if you provide a property called dateCreated and/or one called lastUpdate, GORM will automatically populate the values for these every time an instance is saved or updated. In fact, you've already been using this feature since the Album class has lastUpdated and dateCreated properties. However, if you prefer to manage these properties manually, you can disable automatic time stamping using the autoTimestamp method of the mapping block, as shown in Listing 10-52.

Listing 10-52. Disable Auto Time Stamping

class Album {
    ...
    static mapping = {
        autoTimestamp false
    }
}

Summary

And with that, you've reached the end of this tour of GORM. As you've discovered, thanks in large part to Hibernate, GORM is a fully featured dynamic ORM tool that blurs the lines between objects and the database. From dynamic finders to criteria, there is a plethora of options for your querying needs. However, it's not all clever tricks; GORM provides solutions to the harder problems such as eager fetching and optimistic locking.

Possibly the most important aspect of this chapter is the knowledge you have gained on the semantics of GORM. By understanding the ORM tool you are using, you'll find there are fewer surprises along the way, and you'll become a more effective developer. Although GORM pretty much eliminates the need for a data access layer like those you typically find in pure Java applications, it doesn't remove the need for a structured way to group units of logic. In the next chapter, we'll be looking at Grails services that provide exactly this. Don't go away!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset