Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails service transactional behaviour

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:

  • If an exception is thrown from any method and there's a transaction in progress, it will be rolled back. For example, if 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.
  • If there's no transaction in progress, one will not be started for methods like dontReadOrWrite
  • If no transaction is in progress when readSomething() is called, a read-only transaction will be started. If a read-write transaction is in progress, it will participate in this transaction.
like image 301
Dónal Avatar asked Jan 23 '12 10:01

Dónal


1 Answers

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:

  1. Move the method being called by the other into a different service class, which will be accessed from your original service class via the Spring proxy.
  2. Leave the method where it is. Declare a member variable in your service class to be of the same type as your service class's interface and make it @Autowired, which will give you a reference to your service class's Spring proxy object. Then when you want to invoke your method with the different transactional behavior, do it on that member variable rather than directly, and the Spring transaction code will fire as you want it to.

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.

like image 87
Tim Avatar answered Oct 17 '22 10:10

Tim