Grails transactions

Post on 13-Aug-2015

114 Views

Category:

Software

4 Downloads

Preview:

Click to see full reader

Transcript

Grails Services, Transactions & Async calls

Service

ServiceBusiness logic goes in services.

Don’t use controllers to do any heavy lifting. They are designed only for controlling application flow and doing data marshaling. All your data access and business logic should happen in transactional services.

Also avoid much logic in Domain classes. Validation logic there can be limited to basic constraints and basic postUpdate and postCreate methods.

Create Servicegrails> create-service com.netflix.Contract

| Created file grails-app/services/com/netflix/ContractService.groovy

| Created file test/unit/com/netflix/ContractServiceTests.groovy

package com.netflix

class ContractService {

def serviceMethod() {

}

}

Session vs TransactionUser Session: Corresponds to time within which the user is logged in to the system.

Hibernate Session: Associated with JDBC Connection.

Hibernate Transaction: ACID compliant boundary for unit of work.

Scope> singleton (default), prototype, request, session

class SomeUsefulService {

// this is a request scoped service

static scope = 'request'

}

Service Injectionclass ContractController {

ContractService contractService

...

}

No need for AutoWire annotation.

Command Objects@grails.validation.Validateableclass LoginCommand { def loginService

String username String password

static constraints = { username validator: { val, obj -> obj.loginService.canLogin(obj.username, obj.password) } }}

Transaction

@Transactional annotationService methods are Transactional by default. However best practice is to add @Transactional annotation to each service

Transaction annotation is similar to Spring’s @Transactional annotation

The @Transactional annotation on the class ensures that all public methods in a service are transactional.

Annotating a service method (and not at class) with @Transactional disables the default Grails transactional behavior for that method. so if you use any annotations you must annotate all methods that require transactions.

Exampleimport grails.transaction.Transactional

@Transactionalclass BookService { @Transactional(readOnly = true) def listBooks() { Book.list() }

def updateBook() { // … }}

No Transaction & ReadOnly@NonTransactional

class EmailService {

//OR static transactional = false

….

}

@Transactional(readOnly = true)

class ReportService {

….

}

withTransactionpackage com.netflix

class ContractService {

// turn off automatic transaction management

static transactional = false

void someServiceMethod() {

Contract.withTransaction {TransactionStatus tx ->

// do some work with the database….

// if the transaction needs to be rolled back, call setRollbackOnly()

tx.setRollbackOnly()

}

}

}

Savepointdef save() {

Album.withTransaction { status ->

def album = Album.get(params.id)

album.title = "Changed Title"

album.save(flush:true)

def savepoint = status.createSavepoint()

...

// something goes wrong

if(hasSomethingGoneWrong()) {

status.rollbackToSavepoint(savepoint)

// do something else

...

} } }

Transaction Propagation :(// Doesn’t work well without additional configuration@Transactionalvoid someMethod(...) { // do some work ... storeAuditData(...)}

@Transactional(propagation=Propagation.REQUIRES_NEW)void storeAuditData(...) { //}http://techbus.safaribooksonline.com/book/programming/9781449324513/4dot-spring/_transactional_services_html

Error Handling

Exceptions and Validationsclass AuthorService { void updateAge(id, int age) { def author = Author.get(id) author.age = age if (author.isTooOld()) { throw new AuthorException("too old", author) } if (!author.validate()) { throw new ValidationException("Author is not valid", author.errors) } }}

Exception on Savedef p = Person.get(1)try { p.save(failOnError: true)}catch (ValidationException e) { // deal with exception

p.errors.allErrors.each { println it }}

LazyInitializationExceptionWhen a transaction is rolled back the Hibernate session used by GORM is cleared. This means any objects within the session become detached and accessing uninitialized lazy-loaded collections will lead to LazyInitializationException

class AuthorController { def authorService def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch(e) { render "Author books ${e.author.books}" } } }

Solution...class AuthorService { void updateAge(id, int age) { def author = Author.findById(id, [fetch:[books:"eager"]]) author.age = age if (author.isTooOld()) { throw new AuthorException("too old", author) } }}

Sample

Asynchronous

Various ways...

● Event mechanism using Platform Core plugin

● JMS Messaging Queues Plugin● Quartz scheduling Plugin● Asynchronous Programming

Asynchronous - Promiseimport static java.util.concurrent.TimeUnit.*import static grails.async.Promises.*Promise p = Promises.task {

// Long running task}p.onError { Throwable err ->

println "An error occured ${err.message}"}p.onComplete { result -> println "Promise returned $result"}

Synchronous - Promiseimport static java.util.concurrent.TimeUnit.*import static grails.async.Promises.*Promise p = task {

// Long running task}…. other tasks// block until result is calleddef result = p.get()// block for the specified timedef result = p.get(1,MINUTES)

PromiseListimport static grails.async.Promises.*import grails.async.PromiseListPromiseList promiseList = tasks([{ 2 * 2 }, { 4 * 4}, { 8 * 8 }])//... some other processesassert [4,16,64] == promiseList.get()

PromiseMapimport grails.async.*

PromiseMap map = new PromiseMap()map['one'] = { 2 * 2 }map['two'] = { 4 * 4 }map['three'] = { 8 * 8 }//Async callmap.onComplete { Map results -> assert [one:4,two:16,three:64] == results}

DelegateAsync Transformation//Sync serviceclass BookService { List<Book> findBooks(String title) { // implementation }}

//Async Serviceimport grails.async.*class AsyncBookService { @DelegateAsync BookService bookService}

DelegateAsync call//Async service call AsyncBookService asyncBookServicedef findBooks(String title) { asyncBookService.findBooks(title) .onComplete { List results -> println "Books = ${results}" }}

Asynchronous GORMimport static grails.async.Promises.*Person.async.list().onComplete { List results -> println "Got people = ${results}"}

PromiseList p = Person.async.getAll(1L, 2L, 3L)List results = p.get()

Promise p1 = Person.async.findByFirstName("Homer")Promise p2 = Person.async.findByFirstName("Bart")Promise p3 = Person.async.findByFirstName("Barney")results = waitAll(p1, p2, p3)

Async and the SessionWhen using GORM async each promise is executed in a different thread. Since the Hibernate session is not concurrency safe, a new session is bound per thread.This means you cannot save objects returned from asynchronous queries without first merging them back into session.

def promise = Person.async.findByFirstName("Homer")def person = promise.get()person.merge()person.firstName = "Bart"

In general it is not recommended to read and write objects in different threads and you should avoid this technique unless absolutely necessary. Also fetch the dependent objects eagerly to avoid LazyInitializationException

Quartzplugins { ... compile ":quartz:1.0.1" ...}

//New commands

grails create-jobgrails install-quartz-config

grails create-job com.netflix.SupplierImport

spock

Testing Services

top related