Checked Exceptions Rollback Transactions Since GORM 6.0.0
June 27, 2017
Tags: #gorm
Groovy has less ceremony than Java. That's crystal-clear in exception handling. Java forces us to handle checked exceptions. Groovy does not force us to handle exceptions that we don't want to handle or that are inappropriate at the current level of code. Any exception we don't handle is automatically passed on to a higher level.
Although the distinction between checked exceptions and runtime exceptions in Groovy is blurred, GORM versions before 6.0.0 did not roll back transactions for checked Exceptions
. However, they rollbacked transactions for runtime exceptions.
Grails® 3.2.0 shipped with GORM 6.0.0. One of the changes introduced by GORM 6.0.0 is that if a transactional method throws an Exception
(both checked or runtime exception), the transaction will automatically be rolled back. Thus, same behavior for checked and runtime exceptions as you may expect in Groovy.
The previous behavior is better illustrated with an example:
Given a Checked Exception
package demo
import groovy.transform.CompileStatic
@CompileStatic
class CustomCheckedException extends Exception {
}
and a RuntimeException
package demo
import groovy.transform.CompileStatic
@CompileStatic
class CustomRuntimeException extends RuntimeException {
}
and the next service:
package demo
import grails.transaction.Transactional
import groovy.transform.CompileStatic
@CompileStatic
@Transactional
class BookService {
void addNewBookChecked() {
def book = new Book(name: 'The definitive guide to grails 3')
book.save()
throw new CustomCheckedException()
}
void addNewBookRuntime() {
def book = new Book(name: 'The definitive guide to grails 3')
book.save()
throw new CustomRuntimeException()
}
void addNewBookRegular() {
def book = new Book(name: 'The definitive guide to grails 2')
book.save()
}
@Transactional(readOnly = true)
int count() {
Book.where { }.count() as int
}
}
The next specification passes for versions of GORM 6.0.0 or beyond.
package demo
import spock.lang.Specification
import spock.lang.Subject
import grails.test.mixin.integration.Integration
@Integration
class BookServiceIntegrationSpec extends Specification {
@Subject
BookService bookService
def "calling a service method which does not throw any exception results in adding a new book"() {
when:
bookService.addNewBookRegular()
then:
bookService.count() == old(bookService.count()) + 1
}
def "calling a service method which throws a runtime exception, rollback transaction and it does not add a new book"() {
when:
bookService.addNewBookRuntime()
then:
thrown CustomRuntimeException
bookService.count() == old(bookService.count())
}
// Fails with GORM versions < 6.0.0
def "calling a service method which throws a Checked exception, rollback transaction and it does not add a new book"() {
when:
bookService.addNewBookChecked()
then:
thrown CustomCheckedException
bookService.count() == old(bookService.count())
}
}