For example, by default the com.g2one.gtunes.Album class maps onto a table called album.. For example, the artist property of the Album class will map to a column called artist_id that c
Trang 1grails.test.TagLibUnitTestCase class that lets you unit test tag libraries but not the markup they generate Simply create a new a unit test in the test/unit/com/g2one/gtunes directory called SubscriptionTagLibTests that extends from the TagLibUnitTestCase class, as shown in Listing 16-29.
Listing 16-29. Using the TagLibUnitTestCase Class
testIsSubscribed and testNotSubscribed
Listing 16-30. Testing the SubscriptionTagLib Class
void testIsSubscribed() {
mockDomain(ArtistSubscription)
def artist = new Artist(name:"Kings of Leon")
def user = new User(login:"testuser")
new ArtistSubscription(artist:artist, user:user).save()
Trang 2mockDomain(ArtistSubscription)
def artist = new Artist(name:"Kings of Leon")
def user = new User(login:"testuser")
A closure can be passed as the body of the tag, as long as it returns a String representing
the body contents In Listing 16-30, either “subscribed” or “notsubscribed” will be written to
the mock out variable OK, with the tests out of the way, the next thing to do is to modify the
grails-app/views/artist/_artist.gsp template to include the new _subscribe.gsp template
Listing 16-31 shows the necessary code changes highlighted in bold
Listing 16-31. Updates to the _artist.gsp Template
<div id="artist${artist.id}" class="artistProfile" style="display:none;">
Trang 3Unfortunately, when you click the link, you’ll receive a “Page not found” 404 error To resolve this issue, you need to implement the server logic for the subscribe and unsubscribe actions that the <g:remoteLink> tags in Listing 16-25 refer to Open the ArtistController class, and add a new action called subscribe that persists a new ArtistSubscription if one doesn’t already exist Listing 16-32 shows an example implementation.
Listing 16-32. Implementing the subscribe Action
def subscribe = {
def artist = Artist.get(params.id)
def user = request.user
if(artist && user) {
def subscription = ArtistSubscription.findByUserAndArtist(user, artist) if(!subscription) {
new ArtistSubscription(artist:artist, user:user).save(flush:true)
}
render(template:"/artist/subscribe", model:[artist:artist])
}
}
As you can see from the code in Listing 16-32, the subscribe action reuses the
_subscribe.gsp template to render an Ajax response to the client The logic in
the SubscriptionTagLib deals with the rest To add the unsubscribe logic, you
simply need to delete the ArtistSubscription instance if it exists, as shown in
Listing 16-33
Listing 16-33. Implementing the unsubscribe Action
def unsubscribe = {
def artist = Artist.get(params.id)
def user = request.user
if(artist && user) {
def subscription = ArtistSubscription.findByUserAndArtist(user, artist) if(subscription) {
Trang 4Implementing Asynchronous E-mail Notifications
Now with users able to subscribe to their favorite artists, it is time to consider the onNewAlbum
method of the StoreService class again Whenever a JMS message is received, you’re going to
need to find all the subscribers for the Artist associated with the passed Album and send an
e-mail to each one
To do this, you first need a reference to the mailService bean, provided by the Mail plugin
installed in Chapter 12, which can be obtained by defining a property of the same name:
def mailService
Next, you need to obtain a list of all the User instances subscribed to the Artist associated
with the Album To do this, you can get a reference to the Artist via the artist property:
def artist = album.artist
Then use a criteria query to obtain a list of users:
def users = ArtistSubscription.withCriteria {
Notice the use of the projections block to specify that you want the result to contain the
user property of each ArtistSubscription found Once you have a list of users, you can now
use the mailService to send an e-mail to each one:
for(user in users) {
mailService.sendMail {
from "notifications@gtunes.com"
to user.email
title "${artist.name} has released a new album: ${album.title}!"
body view:"/emails/artistSubscription", model:[album:album,
artist:artist,
user:user]
}
}
As you can see, the body method is used to specify that the e-mail is to be rendered by a
view called /emails/artistSubscription We’ll return to this view in a moment For
complete-ness, Listing 16-35 contains the full code listing for the onNewAlbum(Album) method
Listing 16-35. The onNewAlbum(Album) Method
void onNewAlbum(Album album) {
try {
def artist = album.artist
Trang 5def users = ArtistSubscription.withCriteria {
Essentially, the StockService is a transactional service class It is using Grails’
transactionManager underneath the surface If you recall, the jmsContainer bean was given a reference to the Grails transactionManager in Listing 16-21 As a reminder, here
is the relevant snippet from grails-app/conf/spring/resources.groovy:
jmsContainer(org.springframework.jms.listener.DefaultMessageListenerContainer) {
Trang 6The last thing to do is to finish up the subscription implementation by providing the view
that renders the e-mail Listing 16-36 shows the grails-app/views/emails/artistSubscription
gsp view
Listing 16-36. The artistSubscription View
<%@ page contentType="text/plain"%>
Dear ${user.firstName} ${user.lastName},
One of your favorite artists ${artist.name} has released
a new album called ${album.title}!
It is available now on gTunes at
<g:createLink controller="album"
action="display"
id="${album.id}" absolute="true" />
Kind Regards,
The gTunes Team
Mixing Groovy and Java with Spring
Although Grails already takes advantage of Groovy’s joint compiler, allowing you to integrate
Java code seamlessly into a Grails application, it is often nice to provide this integration
via Spring
As an example, currently the gTunes application is using some Groovy code to stream
music to the user You can find the relevant code in the stream action of the SongController,
which is shown in Listing 16-37
Listing 16-37. The Stream action of the SongController Class
def file = new File(song.file)
try {
def type = file.name[-3 -1]
response.contentType = "audio/x-${type}"
def out = response.outputStream
def bytes = new byte[BUFFER_SIZE]
Trang 7Figure 16-7. The StreamService.java file
Rather than reading each byte, you could take advantage of the java.nio.channels age that allows optimized file transfer Of course, you could use the java.nio.channels package from Groovy, but we’re currently shooting for maximum performance by writing the class in Java Listing 16-38 shows the implementation of the StreamingService class, which provides a method called streamSong that can be used to transfer the bytes of a Song instance to the given OutputStream
pack-Listing 16-38. The StreamingService Class
package com.g2one.gtunes;
import java.io.*;
import java.nio.channels.*;
import org.apache.commons.logging.*;
Trang 8public class StreamingService
{
private static final int BUFFER_SIZE = 2048;
private static final Log LOG = LogFactory.getLog(StreamingService.class);
File file = new File(song.getFile());
FileInputStream input = null;
One important thing to note is that this Java class references the domain class com.g2one
gtunes.Song, which is written in Groovy Groovy’s joint compiler allows Java classes to resolve
Groovy classes, something that, as of this writing, is not possible in any other dynamic
lan-guage on the JVM The remainder of the code simply obtains a FileChannel instance and then
calls the transferTo method to transfer the file to the response OutputStream
Now you could just use the new operator to create a new instance of the StreamingService
class within the SongController But a nicer way to do this is to use Spring Simply register a
new bean in the grails-app/conf/spring/resources.groovy file for the StreamingService class,
as shown in Listing 16-39
Listing 16-39. Creating a streamingService Bean
streamingService(com.g2one.gtunes.StreamingService)
Trang 9Now to obtain a reference to this bean in SongController, just create the equivalent property:
Summary
This chapter gave you some revealing insight into the inner workings of Grails and its Spring underpinnings Moreover, you have learned that just because Grails embraces Convention over Configuration, it does not mean that configuration is not possible Quite the contrary—every aspect of Grails is customizable thanks to Spring
Grails provides such a clean abstraction over Spring that often users of Grails simply don’t know Spring is there In this chapter, you saw how you can reach out to great Spring APIs, such
as the JMS support, to help you solve commons problems Having said that, Spring is an mous framework that provides far more features and benefits than we could possibly cover
enor-in this chapter There are Sprenor-ing abstractions for pretty much every major Java standard and many of the popular open source projects too
Trang 10If you really want to get to grips with Spring, we recommend you invest some time reading
the excellent reference documentation at http://static.springframework.org/spring/docs/
2.5.x/reference/ or take a look at Apress’ excellent array of Spring books.2 Doing so will help
improve your knowledge of Grails too, because fundamentally Grails is just Spring and
Hiber-nate in disguise! In the next chapter, we’ll look at one of the other major frameworks that Grails
builds on for its persistence concerns: Hibernate
2 Some recent Spring books published by Apress include Pro Spring 2.5 by Jan Machacek et al (Apress,
2008), Spring Recipes: A Problem-Solution Approach by Gary Mak (Apress, 2008), and Pro Java EE
Spring Patterns: Best Practices and Design Strategies Implementing Java EE Patterns with the Spring
Framework by Dhrubojyoti Kayal (Apress, 2008).
Trang 11■ ■ ■
Legacy Integration with
Hibernate
Throughout the book, you have been constructing what is essentially a green field1 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
mismatch2 for years Hibernate is capable of mapping onto more than 90 percent of all
data-base schemas and has broad support for different datadata-base 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
1 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.
2 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.
Trang 12Listing 17-1. Defining the Mapping Closure
underly-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
Listing 17-3. Changing a Column Name Mapping
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`"
Trang 13Mapping simple properties such as title is, well, simple Associations tend to require a
lit-tle 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
con-tains 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
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 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
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
cor-rectly 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
Trang 14association 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
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
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 sim-ply need to change the column that is used to establish the foreign key association, then you
Trang 15can 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
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
associ-ation Figure 17-3 shows an example of how a many-to-many association works if you created
a hypothetical Composer domain class Each Composer has many albums, while each Album has
many composers, making this a many-to-many relationship
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
Trang 16class Album {
static hasMany = [composers:Composer]
static belongsTo = Composer
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 ple, it will assume a java.lang.String maps to a java.sql.Types.VARCHAR SQL type
exam-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
Trang 17In 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
exam-ple, 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
■ Note The example in Listing 17-9 assumes you have the necessary JodaTime JAR files within the lib
direc-tory, 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
Trang 18■ 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
Trang 19Listing 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)
Trang 20def 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 umns to function correctly Now let’s look at how to take advantage of the MonetaryAmount class
col-in gTunes Listcol-ing 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"
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
Trang 21In 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,
respec-tively) 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
Trang 22• 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 lying database
under-• 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 tion with a one-to-one primary key association
conjunc-• 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
<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">
Trang 23If 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
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
com-posite 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
com-posite identifier, as shown in Listing 17-18
Listing 17-18. Configuring a Composite Identifier
class Album implements Serializable {
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
Trang 24can 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 map-ping strategies
Essentially, Hibernate defines two built-in mapping strategies The first, and more mon, 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 flex-ibility and control over the mapping options available to you
com-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
Figure 17-5. The hibernate.cfg.xml file
Trang 25The 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"
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"
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
Trang 26Figure 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 con-vention 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">
Trang 27<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
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
annota-tions 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
Trang 28other generic interfaces Generics were added to Java to increase type safety and avoid essary 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
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
Trang 29Figure 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
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;
}