1. Trang chủ
  2. » Công Nghệ Thông Tin

The definitive guide to grails second edition - phần 9 doc

58 385 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Leveraging Spring
Trường học University of Grails
Chuyên ngành Software Engineering
Thể loại Bài luận
Năm xuất bản 2023
Thành phố Hanoi
Định dạng
Số trang 58
Dung lượng 602,09 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

grails.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 2

mockDomain(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 3

Unfortunately, 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 4

Implementing 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 5

def 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 6

The 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 7

Figure 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 8

public 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 9

Now 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 10

If 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 12

Listing 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 13

Mapping 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 14

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

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 15

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

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 16

class 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 17

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

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 19

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)

Trang 20

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 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 21

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,

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 23

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

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 24

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 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 25

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"

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 26

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 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 28

other 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 29

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

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;

}

Ngày đăng: 13/08/2014, 08:21

TỪ KHÓA LIÊN QUAN