In a Grails app, the default behaviour of service methods is that they are transactional and the transaction is automatically rolled-back if an unchecked exception is thrown. However, in Groovy one is not forced to handle (or rethrow) checked exceptions, so there's a risk that if a service method throws a checked exception, the transaction will not be rolled back. On account of this, it seems advisable to annotate every Grails service class
@Transactional(rollbackFor = Throwable.class)
class MyService {
void writeSomething() {
}
}
Assume I have other methods in MyService
, one of which only reads the DB, and the other doesn't touch the DB, are the following annotations correct?
@Transactional(readOnly = true)
void readSomething() {}
// Maybe this should be propagation = Propagation.NOT_SUPPORTED instead?
@Transactional(propagation = Propagation.SUPPORTS)
void dontReadOrWrite() {}
In order to answer this question, I guess you'll need to know what my intention is:
writeSomething()
calls dontReadOrWrite()
, and an exception is thrown from the latter, the transaction started by the former will be rolled back. I'm assuming that the rollbackFor
class-level attribute is inherited by individual methods unless they explicitly override it.dontReadOrWrite
readSomething()
is called, a read-only transaction will be started. If a read-write transaction is in progress, it will participate in this transaction.Your code is right as far as it goes: you do want to use the Spring @Transactional annotation on individual methods in your service class to get the granularity you're looking for, you're right that you want SUPPORTS for dontReadOrWrite (NOT_SUPPORTED will suspend an existing transaction, which won't buy you anything based on what you've described and will require your software to spend cycles, so there's pain for no gain), and you're right that you want the default propagation behavior (REQUIRED) for readSomething.
But an important thing to keep in mind with Spring transactional behavior is that Spring implements transaction management by wrapping your class in a proxy that does the appropriate transaction setup, invokes your method, and then does the appropriate transaction tear-down when control returns. And (crucially), this transaction-management code is only invoked when you call the method on the proxy, which doesn't happen if writeSomething() directly calls dontReadOrWrite() as in your first bullet.
If you need different transactional behavior on a method that's called by another method, you've got two choices that I know of if you want to keep using Spring's @Transactional annotations for transaction management:
Approach #1 is great if the two methods really aren't related anyway, because it solves your problem without confusing whoever ends up maintaining your code, and there's no way to accidentally forget to invoke the transaction-enabled method.
Approach #2 is usually the better option, assuming that your methods are all in the same service for a reason and that you wouldn't really want to split them out. But it's confusing to a maintainer who doesn't understand this wrinkle of Spring transactions, and you have to remember to invoke it that way in each place you call it, so there's a price to it. I'm usually willing to pay that price to not splinter my service classes unnaturally, but as always, it'll depend on your situation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With