CHAPTER 17

Legacy Integration with Hibernate

Throughout the book, you have been constructing what is essentially a green field18 application. There has been no legacy data to deal with, no database administrators (DBAs) are nagging you, and in general life has been good. Unfortunately, in the real world, many applications do have to be reengineered from existing sources and data.

Shockingly enough, these older projects may not follow the conventions that Grails uses to define its database schema. The database tables may use completely different column and table naming conventions. The strategy used to generate the database identifier may differ from the native one Grails uses by default.

Fortunately, the Hibernate team has been on a mission to solve the object-relational mismatch 19 for years. Hibernate is capable of mapping onto more than 90 percent of all database schemas and has broad support for different database vendors. In this chapter, we'll cover how you can reach out and call upon Hibernate's more advanced features in Grails. First, we'll cover Grails' mapping DSL that provides access to most of the common Hibernate features. Later, we'll delve into writing some Hibernate XML and even EJB 3 annotations with Grails.

Legacy Mapping with the ORM DSL

The most common mismatches experienced with Grails occur when the table and column names that Grails expects don't match the underlying database. You can control most aspects of how Grails maps onto the underlying database using the object-relational mapping (ORM) domain-specific language (DSL).

You've actually already had a chance to take advantage of the ORM DSL in Chapter 10 to control fetch strategies and cache configuration. If you recall, to use the ORM DSL, you need to define a mapping static variable that is assigned a Groovy closure, as shown in Listing 17-1.

Listing 17-1. Defining the Mapping Closure

static mapping = {
      ...
}

Within the body of the closure, you can control how Grails maps classes onto the underlying database. Let's start with looking at how to change table and column names.

Changing Table and Column Name Mappings

To change the table a class maps onto, you can call the table method and pass the name of the table. For example, by default the com.g2one.gtunes.Album class maps onto a table called album. If you wanted to map onto a table called RECORDS instead, you could do so as shown in Listing 17-2.

Listing 17-2. Changing the Table Name

class Album {
      ...
       static mapping = {
             table "RECORDS"
      }
}

You can change the column that individual properties map onto by invoking a method that matches the property name. Then using a named argument, called column, you can set the column name. Listing 17-3 shows an example that maps the title property onto a column called R_TITLE.

Listing 17-3. Changing a Column Name Mapping

class Album {
      String title
      ...
      static mapping = {
             table "RECORDS"
             title column: "R_TITLE"
      }
}

Occasionally, you may run into a scenario where the name of a domain class or a property on a domain class conflicts with a SQL keyword. For example, say you have a domain class called Order. The Order domain class by default maps onto a table called order. The name order conflicts with the SQL ORDER BY syntax. At this point, you have two options. You can rename your domain class, or you can use backticks to escape the name of the table:

table "`order`"

Mapping simple properties such as title is, well, simple. Associations tend to require a little more thought. In the next section, we'll cover how you can change the way different kinds of associations map onto the underlying database.

Changing Association Mappings

Grails has special logic that deals with mapping different kinds of associations onto a database. For a simple one-to-one or many-to-one association, Grails will map a foreign key column. For example, the artist property of the Album class will map to a column called artist_id that contains the foreign key reference for the artist. You can change this in the same way as any simple mapping, as shown in Listing 17-4.

Listing 17-4. Changing a Column Name for a Many-to-One Association

class Album {
         static belongsTo = [artist:Artist]
         ...
         static mapping = {
                artist column: "R_CREATOR_ID"
         }
}

The example in Listing 17-4 maps the artist property to a column called R_CREATOR_ID. A one-to-many association requires a little more thought. First you need to consider whether the one-to-many association is unidirectional or bidirectional. With a unidirectional one-to-many association, GORM will use a join table to associate the two tables, since there isn't a foreign key available on the many side of the association. Figure 17-1 illustrates this.

image

Figure 17-1. How GORM maps a unidirectional one-to-many association

As you can see from Figure 17-1, if the albums association were unidirectional, GORM would create an intermediate artist_albums join table in order to map the association correctly. The album_id column, containing the Album identifier, has a unique constraint applied that ensures the join table can't be used for a many-to-many association. For a unidirectional association to work, a join table must available. However, you can change the name of this join table and the columns it uses to create the join.

To do so, you need to use the joinTable argument on the one side of the association. Listing 17-5 shows an example of using the joinTable argument on the albums property of the Artist class.

Listing 17-5. Using a joinTable Mapping

class Artist {
        static hasMany = [albums:Album]
        ...
        static mapping = {
               albums joinTable:[name:'Artist_To_Records',
                                 key:'Artist_Id',
                                 column:'Record_Id']
        }
}

In the example in Listing 17-5, the joinTable argument is used to map the unidirectional albums association onto a join table called Artist_To_Records. The key argument is used to specify the column to store the identifier of the one side, which in this case is the id property of the Artist. Conversely, the column argument is used to specify the name of the column to store the identifier of the many side.

Crucially, the mapping in Listing 17-5 works only for a unidirectional one-to-many because with a bidirectional one-to-many mapping a join table is not used. Instead, a foreign key association is created. Figure 17-2 shows how GORM maps a bidirectional one-to-many association.

image

Figure 17-2. A bidirectional one-to-many association

As you can see from Figure 17-2, since there is a two-ended association, no join table is necessary. The foreign key column artist_id is used to map the albums association. If you simply need to change the column that is used to establish the foreign key association, then you can do so using the column argument on either end of the association. Listing 17-6 shows an example that uses the column argument with the artist property of the Album class.

Listing 17-6. Changing the Foreign Key for a Bidirectional One-to-Many Association

class Album {
       ...
       static mapping = {
              artist column: "R_Artist_Id"
       }
}

One final relationship type to consider is a many-to-many association. A many-to-many association is mapped using a join table in a similar way to a unidirectional one-to-many association. Figure 17-3 shows an example of how a many-to-many association works if you created ahypothetical Composer domain class. Each Composer has many albums, while each Album has many composers, making this a many-to-many relationship.

image

Figure 17-3. How Grails maps a many-to-many association

You can change the way a many-to-many association maps onto the underlying database using the same joinTable argument used to configure a unidirectional one-to-many association. Listing 17-7 shows an example of changing the table and column names for the relationship that Figure 17-3 models.

Listing 17-7. Changing Table and Column Name Mappings for a Many-to-Many

class Composer {
      static hasMany = [albums:Album]
      static mapping = {
            table "MUSICIAN"
            albums joinTable: "MUSICIAN_TO_RECORD", column: "RECORD_ID"
      }
}
class Album {
      static hasMany = [composers:Composer]
      static belongsTo = Composer
      static mapping = {
           ...
        composers joinTable: "MUSICIAN_TO_RECORD", column: "MUSICIAN_ID"
     }
}

The example in Listing 17-7 will map to a join table called MUSICIAN_TO_RECORD. Figure 17-4 shows an example of what the table looks like.

image

Figure 17-4. The MUSICIAN_TO_RECORD join table

Understanding Hibernate Types

Hibernate by default knows how to persist a range of different common Java types. For example, it will assume a java.lang.String maps to a java.sql.Types.VARCHAR SQL type.

The org.hibernate.Hibernate class contains a number of constants that represent the different types that Hibernate knows about by default. For example, the constant Hibernate.STRING represents the type used to persist String instances by default. The SQL VARCHAR type is typically limited to 255 characters, and in some cases this may not be practical.

Using the ORM DSL, you can change the default type used to map a specific column. Listing 17-8 shows an example of changing the title property of the Album class to a Hibernate.TEXT type.

Listing 17-8. Changing the Hibernate Type

class Album {
        ...
        String title
        static mapping = {
               title type: "text"
        }
}

As Listing 17-8 demonstrates, you can refer to the different Hibernate types by name when doing the mapping. The text type will map the text property to a java.sql.Types.CLOB column.

In addition to this, Hibernate allows you to specify custom implementations of the org.hibernate.usertype.UserType interface to allow Hibernate to persist other types. As an example, say you wanted to use the excellent JodaTime Java date and time API (http://joda-time.sourceforge.net). Hibernate by default doesn't know how to persist instances of the JodaTime API such as the org.joda.time.DateTime class.

Fortunately, JodaTime provides a number of custom UserType implementations that can be used to persist JodaTime objects. Listing 17-9 shows an example that uses the org.joda.time.Duration class to represent the duration of a song, instead of an integer.

Listing 17-9. Using the JodaTime Hibernate UserType

class Song {
       ...
        org.joda.time.Duration duration
        static mapping = {
               duration type: org.joda.time.contrib.hibernate.PersistentDuration
        }
}

Note The example in Listing 17-9 assumes you have the necessary JodaTime JAR files within the lib directory, including the JodaTime Hibernate integration library found at http://joda-time.sourceforge.net/contrib/hibernate/index.html.


With Hibernate types, including custom UserType instances, the choice of underlying SQL type is made by the implementation. The sqlTypes() method of the org.hibernate.usertype.UserType interface is responsible for returning an array of SQL types that the UserType maps to. Why an array? Well, a UserType may use multiple columns for storage, so a type is needed for each column used to store data.

For example, the PersistentDuration class from Listing 17-9 returns an array containing a single entry—the Types.VARCHAR SQL type. If you need to override the SQL type used, then you can use the sqlType argument, as shown in Listing 17-10.

Listing 17-10. Using the sqlType Argument

class Song {
       ...
       org.joda.time.Duration duration
       static mapping = {
              duration type: org.joda.time.contrib.hibernate.PersistentDuration,
                       sqlType: "VARCHAR(120)"
       }
}

Note Be careful when using the sqlType argument because you may lose database independence since you are referring directly to underlying SQL types of the database that sometimes differ from vendor to vendor.


Now let's look at a more complex example. Currently, the gTunes application uses a simple Float to represent the price property of the Album class. Say you wanted to have an object that encapsulates not just the price but also the currency. Listing 17-11 shows the MonetaryAmount class that contains properties for both the value and the currency of a given amount.

Listing 17-11. The MonetaryAmount Class

package com.g2one.gtunes

class MonetaryAmount implements Serializable {
    private final BigDecimal value
    private final Currency currency

    MonetaryAmount(value, Currency currency) {
        this.value = value.toBigDecimal()
        this.currency = currency
    }

    BigDecimal getValue() { this.value }
    Currency getCurrency() { this.currency }

    boolean equals(o) {
        if (!(o instanceof MonetaryAmount)) return false
        return o.value == this.value && o.currency == this.currency
    }

    int hashCode() {
        int result = 23468
        result += 37 * this.value.hashCode()
        result += 37 * this.currency.hashCode()
        return result
    }
}

This class lives in the src/groovy directory, so it is not a persistent domain class. Unfortunately, Hibernate has no way of knowing how to persist instances of the MonetaryAmount class. To get around this, you need to implement a custom UserType. Listing 17-12 shows the code for the MonetaryAmountUserType, which stores the properties of the MonetaryAmount class in two different columns.

Listing 17-12. The MonetaryAmountUserType Hibernate User Type

package com.g2one.gtunes

import java.sql.*
import org.hibernate.*
import org.hibernate.usertype.UserType

class MonetaryAmountUserType implements UserType {

    private static final SQL_TYPES = [ Types.NUMERIC, Types.VARCHAR ] as int[]

    public int[] sqlTypes() { SQL_TYPES }
    public Class returnedClass() { MonetaryAmount }
    public boolean equals(x, y) { x == y }
    public int hashCode(x) { x.hashCode() }
    public Object deepCopy(value) { value }
    public boolean isMutable() { false }

    Serializable disassemble(value) { value }
    def assemble(Serializable cached, owner) { cached }
    def replace(original, target, owner) { original }



    public Object nullSafeGet(ResultSet resultSet,
                              String[] names,
                              Object owner)
            throws HibernateException, SQLException {
        if (resultSet.wasNull()) return null

        def value = resultSet.getBigDecimal(names[0])
        def currency = Currency.getInstance(resultSet.getString(names[1]))
        return new MonetaryAmount(value, currency)
    }

    void nullSafeSet(PreparedStatement statement,
                            Object amount,
                            int index) {

        if (amount == null) {
            statement.setNull(index, SQL_TYPES[index])
            statement.setNull(index + 1, SQL_TYPES[index + 1])
        }
        else {
            def currencyCode = amount.currency.currencyCode
            statement.setBigDecimal(index, amount.value)
            statement.setString(index + 1, currencyCode)
        }
    }
}

The crucial parts of the code in Listing 17-12 are the implementations of the nullSafeGet and nullSafeSet methods. The nullSafeGet method is responsible for reading the java.sql.ResultSet and creating a new instance of the target type:

def value = resultSet.getBigDecimal(names[0])
def currency = Currency.getInstance(resultSet.getString(names[1]))
return new MonetaryAmount(value, currency)

The nullSafeSet method is used to populate the PreparedStatement used to store an instance of the target type. The last argument of the nullSafeSet method is the current index, which you can use to set ordinal-based arguments on the PreparedStatement instance:

def currencyCode = amount.currency.currencyCode
statement.setBigDecimal(index, amount.value)
statement.setString(index + 1, currencyCode)

One final thing to note is the definition of the SQL types used:

private static final SQL_TYPES = [ Types.NUMERIC, Types.VARCHAR ] as int[]

Since there are two entries in the array, the MonetaryAmountUserType will require two columns to function correctly. Now let's look at how to take advantage of the MonetaryAmount class in gTunes. Listing 17-13 shows the updates to the com.g2one.gtunes.Album class.

Listing 17-13. Using Custom User Types

class Album {
     MonetaryAmount price
     ...
     static mapping = {
        price type: MonetaryAmountUserType, {
               column name: "price"
               column name: "currency_code"
        }
    }
}

As you can see from Listing 17-13, you can use the type argument to specify the MonetaryAmountUserType implementation. Then you need to configure the mapping of the columns used by a MonetaryAmountUserType by passing a closure. Within the body of the closure, you can set the column names used. Notice that order of the column definitions must match the order of the values returned by the sqlType() method of the MonetaryAmountUserType class.

In addition, if you need to override the underlying SQL type used by each of the columns in the MonetaryAmountUserType class, then you can use the sqlType argument you saw earlier. Listing 17-14 shows an example.

Listing 17-14. Using sqlType with Custom User Types

class Album {
     MonetaryAmount price
     ...
     static mapping = {
        price type: MonetaryAmountUserType, {
               column name: "price"
               column name: "currency_code", sqlType: "text"
        }
    }
}

Changing the Database Identity Generator

The default strategy that GORM uses to obtain an identifier for a newly persisted domain class instance is to use the native database generator. The actual algorithm chosen depends on the capabilities of the underlying database. For example, in MySQL GORM will ask the database to generate an identifier from the id column in a given table.

Many databases don't use an identity column, instead relying on other techniques such as sequences or user-generated identifiers. Fortunately, with Hibernate there is a nice API for defining the identifier generation strategy that is accessible through GORM. Extracted from the Hibernate documentation, this is a list of available identity generators:

  • increment: Generates identifiers of type long, short, or int that are unique only when no other process is inserting data into the same table. This strategy should not be used with Grails since multiple threads accessing the table could result in non-unique identifiers.
  • identity: Supports identity columns in DB2, MySQL, MS SQL Server, Sybase, and HypersonicSQL. The returned identifier is of type long, short, or int.
  • sequence: Uses a sequence in DB2, PostgreSQL, Oracle, SAP DB, and McKoi, or uses a generator in Interbase. The returned identifier is of type long, short, or int.
  • hilo: Uses a high/low algorithm to efficiently generate identifiers of type long, short, or int, given a table and column (by default hibernate_unique_key and next_hi, respectively) as a source of high values. The high/low algorithm generates identifiers that are unique only for a particular database.
  • seqhilo: Uses a high/low algorithm to efficiently generate identifiers of type long, short, or int, given a named database sequence.
  • uuid: Uses a 128-bit UUID algorithm to generate identifiers of type string, unique within a network (the IP address is used). The UUID is encoded as a string of hexadecimal digits of length 32.
  • guid: Uses a database-generated GUID string on MS SQL Server and MySQL.
  • native: Picks identity, sequence, or hilo depending upon the capabilities of the underlying database.
  • assigned: Leaves the application to assign an identifier to the object before save() is called.
  • select: Retrieves a primary key assigned by a database trigger by selecting the row by some unique key and retrieving the primary key value.
  • foreign: Uses the identifier of another associated object. This is usually used in conjunction with a one-to-one primary key association.
  • sequence-identity: A specialized sequence generation strategy that utilizes a database sequence for the actual value generation but combines this with JDBC 3's getGeneratedKeys to actually return the generated identifier value as part of the insert statement execution. This strategy is known to be supported only on Oracle 10g drivers targeted for JDK 1.4.

As you can see, you can choose form many different options, the details of which are covered in far more detail in the Hibernate reference documentation at http://www.hibernate.org/hib_docs/reference/en/html/mapping.html#mapping-declaration-id-generator. Nevertheless, as an example of configuring a custom generator in Grails, Listing 17-15 shows how to configure a hilo generator.

Listing 17-15. Configuring a hilo Generator

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

The example in Listing 17-15 uses a hilo generator that uses a table called hi_value that contains a column called next_value to compute an identifier. If you are familiar with Hibernate, you will have noticed that the map passed to the params argument is equivalent to the <param> element in Hibernate XML mapping. For example, to achieve the equivalent mapping in Hibernate XML, you could use the XML in Listing 17-16.

Listing 17-16. Configuring hilo Generator in XML

<id name="id" type="long" column="cat_id">
        <generator class="hilo">
                <param name="table">hi_value</param>
                <param name="column">next_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>

If the target database doesn't have a numeric identifier but instead uses assigned String values as identifiers, then you can use the assigned generator. For example, say you wanted to use the title property of the Album class as the identifier instead. You could do so with the code in Listing 17-17.

Listing 17-17. Configuring an assigned Generator

class Album {
          String title
          ...
          static mapping = {
                id name: "title", generator: "assigned"
          }
}

The name argument is used to signify the name of the property used for the identifier, while the generator argument is set to assigned.

Using Composite Identifiers

Staying on the topic of identifiers, with Grails you can also use composite identifiers. A composite identifier is an identifier consisting of two or more unique properties of a domain class. For example, the Album domain class could use the title and artist properties to form a composite identifier, as shown in Listing 17-18.

Listing 17-18. Configuring a Composite Identifier

class Album implements Serializable {
       String title
       Artist artist
        ...
       static mapping = {
            id composite:[ "title","artist"]
       }
}

In Listing 17-18, the composite argument is used to pass a list of property names that form the composite primary key. To retrieve domain instances that utilize a composite identifier, you need to pass an instance of the domain class to the get method. For example, given the composite identifier from Listing 17-18, you can use the following code to retrieve an Album instance:

def a = Artist.findByName("Tool")
def album = Album.get(new Album(artist:a, title: "Lateralus"))

Note that when using composite identifiers, your domain class must implement the java.io.Serializable interface; otherwise, you will receive an error. And that completes this tour of the mapping features available through GORM. In the next section, we'll cover how you can use raw Hibernate to achieve a lot of what you've seen so far, and you'll learn how to access the full range of Hibernate configuration options.

Mapping with Hibernate XML

So far in this chapter, you saw how Grails integrates with Hibernate by providing an alternative mapping mechanism that uses convention instead of configuration. What's not tackled in that chapter is that this integration doesn't preclude you from using one of Hibernate's other mapping strategies.

Essentially, Hibernate defines two built-in mapping strategies. The first, and more common, is to use XML mapping files that define how an object is mapped to its related database table. In the next section, you will see how this can be achieved with Grails to gain greater flexibility and control over the mapping options available to you.

Although Hibernate XML is not nearly as concise and simple to work with as GORM, what it does provide is flexibility. It allows fine-grained control over how a class is mapped onto the underlying database, giving you access to the mapping features not available in the ORM DSL.

An important point is that you don't have to map all classes with Hibernate; you can mix and match where you think it's appropriate. This allows GORM to handle the typical case and allows Hibernate to do the heavy lifting.

To get going, the first thing you need to do is create the hibernate.cfg.xml file within the grails-app/conf/hibernate directory of the gTunes application. Figure 17-5 shows an example of how do to this.

image

Figure 17-5. The hibernate.cfg.xml file

The hibernate.cfg.xml file serves to configure the Hibernate SessionFactory, the class that Hibernate uses to interact with the database via sessions. Grails, of course, manages all this for you via the dynamic persistent methods discussed in Chapters 3 and 10.

All we're concerned with at this point is mapping classes from the domain model onto tables in a database. As it stands, the content of the hibernate.cfg.xml file looks something like Listing 17-19.

Listing 17-19. The hibernate.cfg.xml File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
                    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                    http://hibernate.sourceforge.net/hibernate-configuration-
3.0.dtd">
<hibernate-configuration>
    <session-factory>
           <!-- Mapping goes here -->
    </session-factory>
</hibernate-configuration>

At the moment, there is just an empty configuration file. To map individual classes, it is good practice to create individual mapping files for each class and then refer to them in the main hibernate.cfg.xml file.

Listing 17-20 shows how you can use the <mapping> tag within the hibernate.cfg.xml file to achieve this.

Listing 17-20. Adding Mapping Resources to hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
                    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                  "http://hibernate.sourceforge.net/hibernate-configuration-
3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <mapping resource="com/g2one/gtunes/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

The additional mapping is defined in bold with a new mapping resource reference for the com.g2one.gtunes.User class. Of course, the User.hbm.xml file does not yet exist at this point, so you need to create it. Figure 17-6 demonstrates what the state of the directory structure should look like after you've created the mapping file.

image

Figure 17-6. Hibernate config with mapping files

Mapping files contain the actual mappings between the object model and relational table. They're normally located in the same package as the mapped class and follow the naming convention of the class. For example, the mapping file that handles the User mapping is User.hbm.xml, the contents for which are shown in Listing 17-21.

Listing 17-21. The User.hbm.xml Mapping File

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
         "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
         "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
  <class name="com.g2one.gtunes.User" table="user_table" lazy="true">
      <comment>The User domain object</comment>

     <id name="id" column="user_id">
           <generator class="native"/>
      </id>
      <natural-id mutable="true">
            <property name="login"
                            length="10"/>
      </natural-id>
      <property name="password"
                     not-null="true"
                     column="u_pwd"/>
      <property name="email"/>
      <property name="firstName" column="u_first_name"/>
      <property name="lastName" column="u_last_name"/>
      ...
  </class>
</hibernate-mapping>

Listing 17-21 shows how you can map the User class onto a table called user_table that has a natively generated identifier and a natural identifier. A natural identifier, demonstrated by the use of the <natural-id> tag in Listing 17-21, is a property or a combination of properties that is unique to an instance of the User class. In this case, the login property is unique for each User instance and hence has been identified as the natural identifier.

Hibernate will create unique and not-null constraints when creating the database schema for the natural identifier.

Additionally, Hibernate recommends implementing equals and hashCode based on the natural id where possible. In fact, this is recommended even without Hibernate in place. Listing 17-22 shows the amends made to the User domain class to complete this example.

Listing 17-22. User Domain Class Modifications

class User {
      ...
     boolean equals(obj) {
          if(this==obj) return true
          if(!obj || obj.class != this.class) return false
          return login?.equals(obj.login) ? true : false
     }
     int hashCode() {
           return login ? login.hashCode() : super.hashCode()
     }
}

Although not strictly necessary, or even enforced by Hibernate, implementing equals and hashCode will help Hibernate behave correctly when dealing with collections of objects and querying.

With that strategy covered, let's move onto another alternative mapping strategy that uses annotations in the next section.

EJB 3–Compliant Mapping

In its latest incarnation, version 3.0, EJB has drawn inspiration from Hibernate. In many senses, EJB 3.0 responds to Hibernate's market dominance by offering an API that has the same feel as Hibernate but is vendor neutral.

One part of the specification is the Java Persistence API (JPA) that defines a set of annotations for persisting POJO objects using object-relational mapping. Although Grails doesn't support JPA directly (this support is still on the road map at the time of writing), what you can do is write EJB 3.0–compliant entity beans using JPA annotations. As well as annotations, JPA uses Java's Generics feature to establish the type of objects contained within collections and other generic interfaces. Generics were added to Java to increase type safety and avoid unnecessary casting when working with generic objects such as the collections API.

In the next example, you'll use JPA annotations to map a Java version of the com.g2one.gtunes.Address class within the gTunes.

To get started, make sure you have the gTunes application imported into Eclipse as described in Chapter 3.

Next, create new Address Java class in com.g2one.gtunes under the src/java tree using the New Java Class dialog box shown in Figure 17-7.

image

Figure 17-7. The Eclipse New Java Class dialog box

Figure 17-7 shows how to create the Address class within the aforementioned package. Once the Address class has been created, Eclipse's Package Explorer shows a source structure something like Figure 17-8.

image

Figure 17-8. The Address class

Now it's time to write some Java code. First open the Address class and add the @Entity annotation to the class name. To get the class to compile correctly, you will need to import the javax.persistence.Entity annotation as per Listing 17-23.

Listing 17-23. The Class Address.java

package com.g2one.gtunes;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name="address_table")
public class Address {
  // class body
}

As the previous code example shows, you will also need to use the @Table annotation to specify the table name previously used in the section "Mapping with Hibernate XML."

Now create private fields that match the names of the previous Address GORM domain class as per Listing 17-24.

Listing 17-24. The Address Entities Fields

package com.g2one.gtunes;
...
public class Address {
     private Long id;
     private Long version;
     private String number;
     private String street;
     private String city;
     private String state;
     private String postCode;
     private String country;
}

Once this has been done, you need to create public getters and setters for each field so that the Address class becomes a fully fledged JavaBean. Eclipse can help you here by accessing the Source/Generate Getters and Setters menu option. This will make each field into what is known as a property.

Once the properties have been generated, you need to annotate certain properties with additional information defining their purpose, starting with the id property in Listing 17-25.

Listing 17-25. The id Property

@Id
@Column(name="address_id")
@GeneratedValue
public Long getId() {
     return id;
}

In Listing 17-25 the id property is the primary key, it maps to a column called address_id, and its value is generated natively by the database. Next, the Address class's version property needs to be annotated with the @Version annotation. The @Version annotation is needed to specify the property used for optimistic locking (see Chapter 10 for more information about optimistic locking). Listing 17-26 shows how to annotate the getVersion() method with the @Version annotation.

Listing 17-26. The login Property

@Version
public Long getVersion() {
      return version;
}

The city, country, and postcode properties require different column names. Again, you can adjust this by using the @Column annotation, as shown in Listing 17-27.

Listing 17-27. Mapping Individual Columns

@Column(name="a_city")
public String getCity() {
     return city;
}
@Column(name="a_country")
public String getCountry() {
      return country;
}
@Column(name="a_post_code")
public String getPostCode() {
      return postCode;
}

You will, of course, have to add the imports for the @Column, @GeneratedValue, and @Id annotations used so far, but Eclipse or any good IDE will likely do this for you. Once complete, the new EJB 3–compliant Address class will look like the following:


package com.g2one.gtunes;

import javax.persistence.*;

@Entity
@Table(name="address_table")
public class Address {
    private Long id;
    private Long version;
    private String number;
    private String street;
    private String city;
    private String state;
    private String postCode;
    private String country;


    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    @Version
    public Long getVersion() {
        return version;
    }
    public void setVersion(Long version) {
        this.version = version;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getNumber() {
        return number;
    }
    public void setNumber(String number) {
        this.number = number;
    }
    public String getPostCode() {
        return postCode;
    }
    public void setPostCode(String postCode) {
        this.postCode = postCode;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }


}

With that rather long listing out of the way, there are a few things left to do to complete the migration to an EJB 3.0 entity. First, you need to update each DataSource in the grails-app/conf directory to tell Grails that you want to use an annotation configuration strategy. Listing 17-28 shows the necessary changes to the development DataSource.

Listing 17-28. Specifying the Annotation Configuration Strategy

import org.codehaus.groovy.grails.orm.hibernate.cfg.*
class DevelopmentDataSource { // Groovy
   def configClass = GrailsAnnotationConfiguration
   ...
}

With that done, the hibernate.cfg.xml file located in the grails-app/conf/hibernate directory needs updating to reflect the fact that you are no longer using Hibernate XML mapping. Listing 17-29 shows the update Hibernate configuration file with each class referenced using the class attribute of the mapping tag.

Listing 17-29. Updated hibernate.cfg.xml File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
                  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
          <mapping package="com.g2one.gtunes"/>
          <mapping class="com.g2one.gtunes.Address" />
    </session-factory>
</hibernate-configuration>

The previous mapping simply tells Hibernate which classes are persistent, and the configuration of the mapping is then delegated to the annotations contained within the classes themselves.

You now need to delete the Groovy version of the Address domain class in grails/domain, since its Java equivalent has superseded it.

You can now start Grails using grails run-app, and the gTunes application will operate as if nothing has changed. The remarkable thing here is that the Address class is written in Java, and yet all of the dynamic finder and persistence methods work as if by magic.

Grails is an unobtrusive framework, and by that we mean it doesn't require your domain objects to have any knowledge of the framework itself. Grails will magically inject the necessary behavior to support dynamic persistence and query methods into each EJB 3.0 entity using Groovy's Meta Object Protocol.

Grails' unobtrusive nature makes it an appealing proposition, because you can essentially reuse an existing Hibernate domain model and get all the benefits of the dynamic nature of Grails when you need to. On the other hand, if you want to reuse the domain model and mapping from an older application, you can use the same domain model, because there are no framework specifics tying domain objects to Grails.

This is an incredibly powerful concept and one of the defining aspects of Grails that sets it apart from other frameworks and allows you to adopt a blended approach. By blended we mean having the choice to use static typing when you want to or, if you so choose, harnessing the power of dynamic typing when it suits your needs.

Before we continue, there is one thing you should do just to be sure that your application is working exactly as it was before you migrated the domain model: execute the tests. In fact, if you execute grails test-app at this point, you will get a number of failures. Why? During the migration you lose the power that GORM's constraints mechanism offers.

So, how do you create constraints for EJB 3 entities? In the next section we tackle this very issue.

Using Constraints with POJO Entities

Clearly, one of the powerful features of Grails is its constraints mechanism (discussed in Chapter 3). It allows a flexible way to specify metainformation about a class that can then be used in features such as validation and scaffolding. The reason there are failing test cases, as mentioned at the end of the previous section, is that your validation logic for the Address class is no longer working because of missing constraints.

Why is it not working? Quite simply, it's because it is not there! Java doesn't support closures or builders, so you can't just include the necessary code inside a Java class. Luckily, however, Grails has an elegant solution to this problem, again based on the convention approach.

What you need to do is create a new Groovy script in the same package and directory (yes, a Groovy file is now placed under src/java/...) as the class for which the constraints are being applied. The scripts name needs to start with the name of the class and end with Constraints. Figure 17-9 shows a new Groovy script called AddressConstraints.groovy.

The reason the previous file is a script is that it doesn't make sense to define an entirely new class just to define constraints on an existing class. All you are really interested in is applying a set of constraints in the same form as shown inside GORM classes. Listing 17-30 shows how to apply constraints within the AddressConstraints.groovy script.

That is all the file contains: no class definition, no configuration, just the constraints that apply to the Address class. At runtime, Grails will load and execute this script to retrieve the constraints that apply to the Address class, hence allowing Java domain classes to have constraints in the same format as GORM classes.

image

Figure 17-9. Creating the AddressConstraints.groovy script

Listing 17-30. Applying Constraints to the Address Class

package com.g2one.gtunes
constraints = {
     number blank:false, maxSize:200
     street blank:false, maxSize:250
     city blank:false, maxSize:200
     postCode blank:false, maxSize:50
     country blank:false, maxSize:200
}

Summary

In this chapter, you learned the fundamental message behind Grails, even if you didn't realize it until now. Grails strives to make the common, repetitive tasks that Java developers face every day ridiculously simple.

On the other hand, Grails provides all the underlying power and flexibility that you get in traditional Java web frameworks. Need to integrate with an LDAP directory? No problem. Want to expose a SOAP API onto a Grails service? That's possible too. In fact, whatever you can configure with Spring can be integrated with Grails.

In addition, you found out that you can write your domain model in Java and still take advantage of all the advanced Grails features such as dynamic finders, criteria, and scaffolding. Grails takes integration with Java extremely seriously, with the whole goal being to provide an environment for blended development. This also makes committing to Grails a safe choice, since you can always use Java where deemed necessary.

The reality is that there are many cases where static typing is the better choice, and conversely, there are many where dynamic typing is favorable. Groovy and Grails provide a platform to use a mix of approaches that allows you to switch between environments without requiring a large mental shift or making you deal with incompatibilities between programming platforms and paradigms.



18. In software engineering jargon, a green field project is one that is free of any constraints imposed by prior work. See http://en.wikipedia.org/wiki/Greenfield_project.

19. Object-relational mismatch is a term used to describe the technical difficulties in mapping an object-oriented-programming language onto a relational database system; see http://en.wikipedia.org/wiki/Object-Relational_impedance_mismatch.

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

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