Transaction handling is one of the more complex areas of web development. Anytime a user takes any action in the interface which demands a couple of database actions in the backend, then usually you end up having do it as transaction. For a user, everything is either a success or failure. Partial success may be either harmful to the system or doesn’t mean anything to the user. Grails, since it is built on top of Spring and Hibernate, uses their underlying mechanism to deal with transactions. While it may seem confusing in the beginning, Grails actually makes it even more easier.
Lets see it in code.
Suppose we are designing an Offers System, like the one that your bank sends notifying you of some offers and promotions on services. A simplified object structure may be like the following:
1.
class
Offer {
2.
String title
3.
String content
4.
Date expirationDate
5.
Date createdDate =
new
Date()
6.
7.
static
hasMany = [recipients: Recipient]
8.
}
1.
class
Recipient {
2.
String email
3.
4.
static
belongsTo = [offer: Offer]
5.
}
The relationship is fairly simple. Each Offer can have many Recipients, each Recipient belongs to an Offer.
belongsTo just means the offer Recipient will be deleted whenever the Offer is deleted, which makes sense coz we don’t want to keep the junk if the Offer itself is deleted.
hasMany lets you access to the Recipients from the Offer object. In other words, if you have the Offer object, you can dooffer.recipients and get the list of all the recipients for the Offer. Cool.
Now, here is how it will work. We want to add an Offer and some Recipients. Everything should either succeed or fail. Even if only one Recipient out of thousands fail, everything should fail.
There are two ways to do transactions in Grails:
1. Method-level Transaction: By default, each Service class in grails has a transactional property set to true. So if any public method in a Service class throws a RuntimeException or any Error, the transaction will be rolled back. If you do not need this default transaction property, you can explicitly set transactional to false.
In the code below, the OfferService throws a Runtime Exception anytime it isn’t able to add the Fffer or the Recipient, and the transaction will be rolled back.You wouldn’t get the same transactional behavior if the method throws any Checked Exception or if the transactionalproperty is set to false.
01.
class
OfferService {
02.
03.
boolean
transactional =
true
04.
05.
def save(offer, recipients) {
06.
if
(!offer.validate()) {
07.
throw
new
RuntimeException(
"Invalid offer."
)
08.
}
else
{
09.
def result = offer.save()
10.
recipients.each {
11.
if
(!it.validate()) {
12.
throw
new
RuntimeException(
"Invalid recipient"
)
13.
}
else
{
14.
offer.addToRecipients(it)
15.
}
16.
}
17.
}
18.
}
19.
}
You can then catch the exception in the Controller and render a more user-friendly error message.
01.
class
OfferController {