Grails: a quick tutorial (1)

download Grails: a quick tutorial (1)

If you can't read please download the document

Transcript of Grails: a quick tutorial (1)

Blu scuro

Grails: a quick tutorial (1)

Davide Rossi

Jug Milano, 22/01/2009

Contents

- Grails: Groovy on Rails ?

- Create a new application

- Create domain classes

- Datasource

- Constraints

- Associations

- Create controllers / views

- Run the application

Contents (2)

- Searching

- Dynamic finders

- Hql (Hibernate query language)

- Hibernate Criteria

- Pagination

Grails: Groovy on Rails ?

- Web MVC framework based on Groovy

- Inspired by Ruby on Rails

- Built on top of Spring, Spring MVC, Hibernate

- Convention over Configuration

- Integrated Jetty Server and HSQL DB for rapid development

- Produces a WAR

Creating a new application

A simple CRUD application, a teams / players database

> grails create-app JugDemo

Domain classes and GORM

- Domain classes are the model layer of the application

- Every domain class is automatically persisted by GORM (Grails' Object Relational Mapping)

- GORM is based on Hibernate

- GORM can persist Java classes

- GORM adds id and version properties

- GORM creates DB schema from Domain classes

- GORM allows to configure table mapping (useful for legacy DB)

Datasource

dataSource { pooled = true driverClassName = "org.hsqldb.jdbcDriver" username = "sa" password = ""}hibernate { cache.use_second_level_cache=true cache.use_query_cache=true cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'}

Datasource

environments {development {dataSource {dbCreate = "create-drop" // one of 'create', 'create-drop','update'url = "jdbc:hsqldb:mem:devDB"}}test {dataSource {dbCreate = "update"url = "jdbc:hsqldb:mem:testDb"}}production {dataSource {dbCreate = "update"url = "jdbc:hsqldb:file:prodDb;shutdown=true"}}}

Constraints

- Grails validation (from Spring)

- Used by GORM to generate DB schema

- To validate a class instance call validate()

- save() calls validate() before calling the DB

- blank, creditCard, email, inList, matches, max, maxSize, min, notEqual, nullable, range, scale, size, unique, url, validator (custom closure)

Team domain class

> grails create-domain-class team

class Team { String name String championship String city Long supporters Integer foundationYear

static hasMany = [players: Player]

static constraints = { name (unique: true, blank: false, size: 2..30) championship (blank: false, inList: ['Serie A', 'Serie B', 'Serie C1', 'Serie C2']) city (blank: true, size: 2..25) supporters (nullable: true) foundationYear (nullable: true) }}

Player domain class

> grails create-domain-class player

class Player { String name String lastName String city Date birth String email

static belongsTo = [team: Team]

static constraints = { name (blank: false, size: 2..30) lastName (blank: false, size: 2..30) city (blank: true, size: 2..25) birth (nullable:true) email (blank: true, email: true) }}

GORM Associations One to one

Unidirectional- class Person { BillingAddress address } class BillingAddress { ... }

Bidirectional (no update / delete cascading)- class Person { BillingAddress address } class BillingAddress { Person person }

GORM Associations One to one

Bidirectional (update / delete cascading)

- class Person { BillingAddress address }

class BillingAddress { static belongsTo = [person: Person] }

GORM Associations One to many

Update cascading, no delete- class Team { static hasMany = [players: Player] } class Player { ... }

Update / delete cascading- class Team { static hasMany = [players: Player] } class Player { static belongsTo = [team: Team] }

GORM Associations Many to many

Update / delete cascading

- class Author {

static hasMany = [books: Book]

}

class Book {

static belongsTo = Author

static hasMany = [authors: Author]

}

- GORM creates a join table with the entitiy names (author_book)

GORM notes

- Default fetching strategy is lazy but can be changed

- Default inheritance strategy is table per hierarchy, but can be changed to table per class

- Its possible to prevent some properties from being persisted: static transients = [ "myProperty" ]

- Provides events hooks: beforeInsert, beforeUpdate, onLoad...

Team controller

> grails generate-controller team

Every action is implemented as a closure

Team controller - List

def list = { if (!params.max) params.max = 10 [ teamList: Team.list( params ) ]}

- params is a map containing the request parameters

- list is a GORM method, can take parameters:- max, offset, order, sort, ignoreCase, fetch

- No explicit return statement

- No explicit view

Team controller - Show

def show = { def team = Team.get( params.id ) if (!team) { flash.message = "Team not found with id ${params.id}" redirect(action: list) } else { return [ team : team ] }}

- get is a GORM method

- flash is a new scope introduces by Grails

- explicit return statement

Team controller - Save

def save = { def team = new Team(params) if(!team.hasErrors() && team.save()) { flash.message = "Team ${team.id} created" redirect(action: show, id: team.id) } else { render(view: 'create', model: [team: team]) }}

- save performs validation and return null if errors are found

- explicit view declarations (render)

Generate controllers and view

> grails generate-views team

> grails generate-all player

Running the app

> grails run-app

Application bootstrap

def init = { servletContext ->

if (GrailsUtil.environment == "development") { def team1 = new Team(name: 'Varese', championship: 'Serie C2', city: 'Varese', supporters: 1000, foundationYear: 1910).save() def team2 = ... def team3 = ... def team4 = ...

new Player(name: 'player1', lastName: 'one', city: 'Varese', birth: new Date(), email: '[email protected]', team: team1).save() ... new Player(name: 'player11', lastName: 'eleven', city: 'Palermo', birth: new Date(), email: '[email protected]', team: team4).save() }}

Search types

- Dynamic finders

- Hql

- Criteria

Adding a search form

team/list.gsp:

Championship:

Dynamic finders

teamController:

def search = { def teamList = Team.findAllByChampionship(params.championship) render(view:'list', model: [teamList: teamList] )}

Dynamic finders

- findBy and findAllBy- def team = Team.findByName(Juventus)- def teamList = Team.findAllByCity(Milano)

- Composition of properties, boolean logic and comparators:- and, or- LessThan, LessThanEquals, GreaterThan, GreaterThanEquals, Like, Ilike, NotEqual, Between, IsNotNull, IsNull

es: Team.findAllByCityLikeAndSupportersGreaterThan(Milano, 1000, [max: 3, offset: 2, sort: "name", order: "desc"])

Multiaction search form

- Each button sends the form to a different action

Hql queries

def teamSearch = { def playerList = Player.findAll("from Player as p where p.team.name = ?", ["${params.searchQuery}"]) render(view: 'list', model: [playerList: playerList])}

- You can use HQL queries with named and pagination parameters:

es:Player.findAll("from Player as p where p.team.name = :teamName order by p.lastName asc", [teamName: "Juventus"], [max:10, offset:20])

Hibernate Criteria

def citySearch = { def c = Player.createCriteria() def playerList = c.list { ilike("city", "%${params.searchQuery}%") }

render(view: 'list', model: [playerList: playerList]) }

- Uses a Groovy builder to hide Hibernate's Criteria API complexity

Hibernate Criteria

A more complex example:

def c = Account.createCriteria()def results = c {like("holderFirstName", "Fred%")and {between("balance", 500, 1000)eq("branch", "London")}maxResults(10)order("holderLastName", "desc")}

Pagination problem

After the search we see a pagination error: we must modify the generated action and gsp:

Pagination

def list = { if (!params.max) params.max = 10 def playerList = Player.list( params ) [ playerList: playerList, playerCount: Player.count()] }

Pagination

def teamSearch = { def playerList = Player.findAll("from Player as p where p.team.name = ?", ["${params.searchQuery}"]) render(view: 'list', model: [playerList: playerList, playerCount: playerList.size()])}

def citySearch = { def c = Player.createCriteria() def playerList = c.list { ilike("city", "%${params.searchQuery}%") }

render(view: 'list', model: [playerList: playerList, playerCount: playerList.size()])}

Conclusions

- Convention over configuration speeds up development by reducing glue and framework code

- GORM is a powerful tool that leverage Hibernate strengths but lowering its complexity

- You can reuse your previous knowledge (HQL, Criteria, Spring Validation...)

Click to edit the title text format

Click to edit the outline text format

Second Outline Level

Third Outline Level

Fourth Outline Level

Fifth Outline Level

Sixth Outline Level

Seventh Outline Level

Eighth Outline Level

Ninth Outline Level

Click to edit the title text format

Click to edit the outline text format