1
Understanding Database Transactions and
Hibernate Sessions in Grails
jonas.witt@valsight.com
@jonaswitt
Jonas Witt
Berlin Groovy User Group
2017-05-11
3
GORM/Hibernate: Introduction
• Hibernate is a very powerful Java Object/Relational
Mapping (ORM) framework that accesses relational
databases via JDBC
• GORM is Grails’ default ORM and uses Hibernate as one of
its backends (others: Mongo, Neo4J, REST)
• Both combined make persistence of your application’s
domain model very convenient – but understanding the
details is essential when using the frameworks in production
4
Today’s Focus: Saving Domain Objects
• This talk will focus on saving domain objects,
i.e. the behavior of abstraction layers between your
author.save() call and (eventually) persistence
• To better understand reading domain objects, consider:
– Using Hibernate caching:
http://gorm.grails.org/6.0.x/hibernate/manual/#advancedGORMF
eatures
– “hasMany Considered Harmful” by Burt Beckwith:
https://www.youtube.com/watch?v=-nofscHeEuU
5
Hibernate Sessions
6
Hibernate: transactional write-behind
class AuthorController {
def sessionFactory
def updateName() {
Author author = Author.get(params.id)
author.name = 'Donald Knuth'
author.save()
sessionFactory.currentSession.flush()
render author as JSON
}
}
7
Hibernate: transactional
write-behind
1) By default, a controller uses a
non-transactional database
connection
2) Saving a GORM object does
not not necessarily flush the
changes to the database –
updates are batched for
better performance
3) session.flush() or save(flush:
true) forces the Hibernate
session to be flushed
Grails Controller Hibernate Session SQL Database
HTTP PUT
(start session)
getConnection() (1)
Author.get(id)
(check 1st level cache)
SELECT * FROM
author WHERE id = ?
author.name =
"Donald Knuth"
author.save() (2)
session.flush() (3)
UPDATE author SET ...
WHERE id = ?
200 OK
8
Hibernate FlushMode
1) In order to guarantee
correctness of the
returned author list,
Hibernate will flush
pending changes
(flushMode = AUTO,
default in GORM < 6.1)
FlushModes: ALWAYS,
AUTO, COMMIT, MANUAL
Grails Controller Hibernate Session SQL Database
HTTP PUT
author.name =
"Donald Knuth"
author.save()
Author.list() (1)
UPDATE author SET ...
WHERE id = ?
SELECT * FROM author
200 OK
10
Hibernate session: summary
• The Hibernate session batches updates to the underlying
database for performance reasons
• FlushMode = AUTO triggers implicit flushes when querying
the database would lead to an outdated response otherwise
• Use explicit flush()/clear() to reduce size of Hibernate session
11
Database Transactions
12
Updating entities without transactions
Problems:
• Concurrent requests will see a state
where only some of the 4 books exists
• When the action fails in between, the
DB will be left in an inconsistent state
class AuthorController {
def createDefaultBooks() {
Author author = Author.get(params.id)
List books = (1..4).collect {
new Book(author: author,
title: "The Art of Computer Programming, Vol. $it")
}
books*.save(flush: true)
render books as JSON
}
}
13
Updating entities without transactions
Grails Controller Hibernate Session SQL Database
HTTP PUT
book1.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
book2.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
render books as JSON
200 OK
14
import grails.transaction.Transactional
class AuthorController {
@Transactional
def createDefaultBooks() {
Author author = Author.get(params.id)
List books = (1..4).collect {
new Book(author: author,
title: "The Art of Computer Programming, Vol. $it")
}
books*.save(flush: true)
render books as JSON
}
}
Updating entities with transactions*
(*) Don’t do this at home!
Transactional behavior: CHECK! !
15
Updating entities with transactions*
(*) Don’t do this at home!
Grails Controller Hibernate Session Transaction SQL Database
HTTP PUT
@Transactional
START TRANSACTION
book1.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
book2.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
(end of action)
COMMIT
200 OK
16
DEMO, pt. 1
17
[https://en.wikipedia.org/wiki/Race_condition]
“Race conditions arise in software when an application
depends on the sequence or timing
of processes or threads for it to operate properly.”
“When adding a sleep() statement makes your program fail,
there is a race condition”
[Some clever programmer, somewhere]
18
DEMO, pt. 2
19
Grails Controller Hibernate Session Transaction SQL Database
HTTP PUT
@Transactional
START TRANSACTION
book1.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
book2.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
(end of action)
COMMIT
render books as JSON
200 OK
Updating entities with transactions*
(*) Don’t do this at home!
20
Updating entities with transactions*
(*) Don’t do this at home!
Grails Controller Hibernate Session Transaction SQL Database
HTTP PUT
@Transactional
START TRANSACTION
book1.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
book2.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
render books as JSON
200 OK
(end of action)
COMMIT
21
Updating
entities with
transactions*
(*) Don’t do this at home!
Grails Controller Hibernate Session Transaction SQL Database
HTTP PUT
@Transactional
START TRANSACTION
book1.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
book2.save(flush: true)
INSERT INTO books VALUES (?, ?, ?)
render books as JSON
200 OK
HTTP GET
SELECT * FROM books
WHERE id = ?
404 Not Found
(end of action)
COMMIT
22
Transactional
by default! ! ! !
class AuthorService {
def createDefaultBooks(Author author) {
List books = (1..4).collect {
new Book(author: author,
title: "The Art of Computer Programming, Vol. $it")
}
books*.save(flush: true)
return books
}
}
class AuthorController {
def authorService
def createDefaultBooks() {
Author author = Author.get(params.id)
def books = authorService.createDefaultBooks(author)
render books as JSON
}
}
23
• The Grails service
provides
@Transactional
behavior by default
• The service method
boundaries
encapsulate the
business logic, and
separate it from the
HTTP request handling
Grails Controller Service Transaction SQL Database
HTTP PUT
SELECT * FROM author WHERE id = ?
@Transactional
START TRANSACTION
INSERT INTO books VALUES (?, ?, ?)
INSERT INTO books VALUES (?, ?, ?)
(end of transactional method)
COMMIT
render books as JSON
200 OK
Updating entities with transactions: best practice
24
SQL transaction isolation
Isolation level Dirty reads
Non-repeatable
reads
Phantom reads
Read Uncommitted may occur may occur may occur
Read Committed don't occur may occur may occur
Repeatable Read don't occur don't occur may occur
Serializable don't occur don't occur don't occur
[https://en.wikipedia.org/wiki/Isolation_(database_systems)]
The default
in many
JDBC drivers
25
SQL transactions summary
• Use transactions to ensure atomic updates of related objects
• Be mindful of transaction boundaries
• Best practice:
– Let Controllers handle the HTTP request only: parse HTTP
parameters / request body, and render the HTTP response
– Services (transactional by default) should be used to manipulate
GORM objects (both light + heavy lifting)
Thank you!
jonas.witt@valsight.com
@jonaswitt
Valsight GmbH
Uhlandstr. 29
10719 Berlin

Understanding Database Transactions and Hibernate Sessions in Grails

  • 1.
    1 Understanding Database Transactionsand Hibernate Sessions in Grails jonas.witt@valsight.com @jonaswitt Jonas Witt Berlin Groovy User Group 2017-05-11
  • 2.
    3 GORM/Hibernate: Introduction • Hibernateis a very powerful Java Object/Relational Mapping (ORM) framework that accesses relational databases via JDBC • GORM is Grails’ default ORM and uses Hibernate as one of its backends (others: Mongo, Neo4J, REST) • Both combined make persistence of your application’s domain model very convenient – but understanding the details is essential when using the frameworks in production
  • 3.
    4 Today’s Focus: SavingDomain Objects • This talk will focus on saving domain objects, i.e. the behavior of abstraction layers between your author.save() call and (eventually) persistence • To better understand reading domain objects, consider: – Using Hibernate caching: http://gorm.grails.org/6.0.x/hibernate/manual/#advancedGORMF eatures – “hasMany Considered Harmful” by Burt Beckwith: https://www.youtube.com/watch?v=-nofscHeEuU
  • 4.
  • 5.
    6 Hibernate: transactional write-behind classAuthorController { def sessionFactory def updateName() { Author author = Author.get(params.id) author.name = 'Donald Knuth' author.save() sessionFactory.currentSession.flush() render author as JSON } }
  • 6.
    7 Hibernate: transactional write-behind 1) Bydefault, a controller uses a non-transactional database connection 2) Saving a GORM object does not not necessarily flush the changes to the database – updates are batched for better performance 3) session.flush() or save(flush: true) forces the Hibernate session to be flushed Grails Controller Hibernate Session SQL Database HTTP PUT (start session) getConnection() (1) Author.get(id) (check 1st level cache) SELECT * FROM author WHERE id = ? author.name = "Donald Knuth" author.save() (2) session.flush() (3) UPDATE author SET ... WHERE id = ? 200 OK
  • 7.
    8 Hibernate FlushMode 1) Inorder to guarantee correctness of the returned author list, Hibernate will flush pending changes (flushMode = AUTO, default in GORM < 6.1) FlushModes: ALWAYS, AUTO, COMMIT, MANUAL Grails Controller Hibernate Session SQL Database HTTP PUT author.name = "Donald Knuth" author.save() Author.list() (1) UPDATE author SET ... WHERE id = ? SELECT * FROM author 200 OK
  • 8.
    10 Hibernate session: summary •The Hibernate session batches updates to the underlying database for performance reasons • FlushMode = AUTO triggers implicit flushes when querying the database would lead to an outdated response otherwise • Use explicit flush()/clear() to reduce size of Hibernate session
  • 9.
  • 10.
    12 Updating entities withouttransactions Problems: • Concurrent requests will see a state where only some of the 4 books exists • When the action fails in between, the DB will be left in an inconsistent state class AuthorController { def createDefaultBooks() { Author author = Author.get(params.id) List books = (1..4).collect { new Book(author: author, title: "The Art of Computer Programming, Vol. $it") } books*.save(flush: true) render books as JSON } }
  • 11.
    13 Updating entities withouttransactions Grails Controller Hibernate Session SQL Database HTTP PUT book1.save(flush: true) INSERT INTO books VALUES (?, ?, ?) book2.save(flush: true) INSERT INTO books VALUES (?, ?, ?) render books as JSON 200 OK
  • 12.
    14 import grails.transaction.Transactional class AuthorController{ @Transactional def createDefaultBooks() { Author author = Author.get(params.id) List books = (1..4).collect { new Book(author: author, title: "The Art of Computer Programming, Vol. $it") } books*.save(flush: true) render books as JSON } } Updating entities with transactions* (*) Don’t do this at home! Transactional behavior: CHECK! !
  • 13.
    15 Updating entities withtransactions* (*) Don’t do this at home! Grails Controller Hibernate Session Transaction SQL Database HTTP PUT @Transactional START TRANSACTION book1.save(flush: true) INSERT INTO books VALUES (?, ?, ?) book2.save(flush: true) INSERT INTO books VALUES (?, ?, ?) (end of action) COMMIT 200 OK
  • 14.
  • 15.
    17 [https://en.wikipedia.org/wiki/Race_condition] “Race conditions arisein software when an application depends on the sequence or timing of processes or threads for it to operate properly.” “When adding a sleep() statement makes your program fail, there is a race condition” [Some clever programmer, somewhere]
  • 16.
  • 17.
    19 Grails Controller HibernateSession Transaction SQL Database HTTP PUT @Transactional START TRANSACTION book1.save(flush: true) INSERT INTO books VALUES (?, ?, ?) book2.save(flush: true) INSERT INTO books VALUES (?, ?, ?) (end of action) COMMIT render books as JSON 200 OK Updating entities with transactions* (*) Don’t do this at home!
  • 18.
    20 Updating entities withtransactions* (*) Don’t do this at home! Grails Controller Hibernate Session Transaction SQL Database HTTP PUT @Transactional START TRANSACTION book1.save(flush: true) INSERT INTO books VALUES (?, ?, ?) book2.save(flush: true) INSERT INTO books VALUES (?, ?, ?) render books as JSON 200 OK (end of action) COMMIT
  • 19.
    21 Updating entities with transactions* (*) Don’tdo this at home! Grails Controller Hibernate Session Transaction SQL Database HTTP PUT @Transactional START TRANSACTION book1.save(flush: true) INSERT INTO books VALUES (?, ?, ?) book2.save(flush: true) INSERT INTO books VALUES (?, ?, ?) render books as JSON 200 OK HTTP GET SELECT * FROM books WHERE id = ? 404 Not Found (end of action) COMMIT
  • 20.
    22 Transactional by default! !! ! class AuthorService { def createDefaultBooks(Author author) { List books = (1..4).collect { new Book(author: author, title: "The Art of Computer Programming, Vol. $it") } books*.save(flush: true) return books } } class AuthorController { def authorService def createDefaultBooks() { Author author = Author.get(params.id) def books = authorService.createDefaultBooks(author) render books as JSON } }
  • 21.
    23 • The Grailsservice provides @Transactional behavior by default • The service method boundaries encapsulate the business logic, and separate it from the HTTP request handling Grails Controller Service Transaction SQL Database HTTP PUT SELECT * FROM author WHERE id = ? @Transactional START TRANSACTION INSERT INTO books VALUES (?, ?, ?) INSERT INTO books VALUES (?, ?, ?) (end of transactional method) COMMIT render books as JSON 200 OK Updating entities with transactions: best practice
  • 22.
    24 SQL transaction isolation Isolationlevel Dirty reads Non-repeatable reads Phantom reads Read Uncommitted may occur may occur may occur Read Committed don't occur may occur may occur Repeatable Read don't occur don't occur may occur Serializable don't occur don't occur don't occur [https://en.wikipedia.org/wiki/Isolation_(database_systems)] The default in many JDBC drivers
  • 23.
    25 SQL transactions summary •Use transactions to ensure atomic updates of related objects • Be mindful of transaction boundaries • Best practice: – Let Controllers handle the HTTP request only: parse HTTP parameters / request body, and render the HTTP response – Services (transactional by default) should be used to manipulate GORM objects (both light + heavy lifting)
  • 24.