Using a REST API project backed by GORM / SQL as an example, this talk gives an overview of the layers that are between the GORM objects that you interact with and their persistence. It talks about how to ensure correctness in your transactions and avoid performance problems in Hibernate sessions.
Watermarking in Source Code: Applications and Security Challenges
Understanding Database Transactions and Hibernate Sessions in Grails
1. 1
Understanding Database Transactions and
Hibernate Sessions in Grails
jonas.witt@valsight.com
@jonaswitt
Jonas Witt
Berlin Groovy User Group
2017-05-11
2. 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
3. 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
6. 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
7. 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
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
10. 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
}
}
11. 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
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 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
15. 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]
17. 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!
18. 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
19. 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
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 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
22. 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
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)