Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

grails how to disable transactional for a method

I have a service method to transfer funds to/from an external system.

it should create a transaction in our system first (so we have a transactionId) Then we call the external system. If the external system fails, we need to rollback the transaction, then write a new record in our payment audit log table, regardless of if the call failed or worked.

I cant figure out how to control the transaction in this case.

I understand services are transactional by default.

I assume I could create 3 methods (they are all 1 method now, which doesn't work as I have no control over what gets committed and what gets rolled back)

  1. createPaymentTransaction()
  2. sendToPaymentSystem()
  3. createPaymentRecord()

I need to rollback 1 if 1 fails, and do nothing more. I need to rollback 1 if 2 fails, but write 3. I need to write 3 if 1 and 2 works.

I don't know how to annotate these, or how to structure a 4th request to manage the 3.

like image 824
John Little Avatar asked Feb 19 '15 16:02

John Little


1 Answers

I'd go with something like this:

package com.myapp

import grails.transaction.Transactional

import org.springframework.transaction.annotation.Propagation

@Transactional
class MyService {

    def createPaymentTransaction() {}

    def sendToPaymentSystem() {}

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    def createPaymentRecord() {}

    def method4() {
        try {
            def transactionId = createPaymentTransaction()
            sendToPaymentSystem(transactionId)
        }
        finally {
            createPaymentRecord()
        }
    }
}

By annotating at the class level, we set the defaults for all methods, but can customize as needed, e.g. for createPaymentMethod.

So what will happen is that calling method4 will join an existing transaction, or start a new one if necessary. If there's a problem in either createPaymentTransaction or sendToPaymentSystem then the transaction will be rolled back, but the call to createPaymentRecord will happen because it's in the finally block, and it will run in a separate transaction so it isn't affected by a rollback in the main transaction, and a failure there doesn't affect the main transaction.

If you're not able to use the new grails.transaction.Transactional annotation, use the standard Spring org.springframework.transaction.annotation.Transactional annotation, but you need to make a small change. One of the motivations for the Grails annotation is to provide the same functionality as the Spring annotation, but avoid the problems with calling an annotated method from within the service. The Spring annotation triggers creation of a proxy at runtime which intercepts all calls, manages transactionality for the method, and then calls the real method in the service instance. But with the current code, the call to createPaymentRecord will bypass the proxy (the service instance is just calling itself) and there won't be a new transaction. The Grails annotation rewrites the bytecode to wrap each method in a transaction template which uses the applicable annotation settings (explicit or inferred from a class-scope annotation), so it works correctly internally and externally. If using the Spring annotation, you need to call the method on the proxy, which just involves accessing the Spring bean for this service. Add a dependency injection for the GrailsApplication as a field:

def grailsApplication

and then call createPaymentRecord via

grailsApplication.mainContext.myService.createPaymentRecord()

in the finally block.

like image 60
Burt Beckwith Avatar answered Nov 06 '22 11:11

Burt Beckwith