Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails services are not transactional?

According to the official documentation, and the books I have read, services are transnational be default. however, we were getting records committed, even if we immediately throw a RuntimeException.

e.g:

class MyService {
    def someMethod() {
        new someDomainObject().save(failOnError:true)
        throw new RuntimeException("rollback!")
    }
}

and calling it thusly:

class myController{
   MyService myService
    def someMethod() {
         myService.someMethod()
    }
}

In the above case, after calling the controller which calls the service, then checking if the row was created by attaching to the DB using mysql workbench, the row was indeed committed and not rolled back.

So we next tried this:

class MyService {
    static transactional = true
    def someMethod() {
        new someDomainObject().save(failOnError:true)
        throw new RuntimeException("rollback!")
    }
}

Same problem.

Next we tried this:

@Transactional
class MyService {
    static transactional = true
    def someMethod() {
        new SomeDomainObject().save(failOnError:true)
        throw new RuntimeException("rollback!")
    }
}

Finally, this works. However, we dont understand why.

Note: Grails 2.4.4 using MYSQL:

development {
    dataSource {
        dbCreate = "create-drop"
        url = "jdbc:mysql://127.0.0.1:3306/db"
        username = "user"
        password = "***"
    }
}

Is this normal behavior?
Is @Transactional different to static tranasctional=true?

The Service classes were generated by intellij 14 using the "new groovy class" option from the Services folder in the Grails view. The "new Grails Service" option does not work for us, it just does nothing, so we have to create all groovy classes "by hand" in the right place.

like image 582
John Little Avatar asked Feb 26 '15 14:02

John Little


2 Answers

OK, found the cause, or Gotcha:

"Annotating a service method with Transactional disables the default Grails transactional behavior for that service"

So I happened to annotate one of the many methods in the service as @Transactional(propagation=Propagation.REQUIRES_NEW), thinking that the others will retain their default of transactional, but no, if you make any declarations, it removes the transactoinal behavior of all other methods silently, even if you say "static transactional = true"

This appears to be rather dangerous, and from now on, I will annotate every service class with @Transactional to avoid being caught out.

like image 123
John Little Avatar answered Sep 23 '22 13:09

John Little


This doesn't make a lot of sense. All of the different variants of the service should function the same. The general logic used is to look for @Transactional at the class level or on at least one method. If you use org.springframework.transaction.annotation.Transactional then a transactional proxy will be created. If you use the newer grails.transaction.Transactional then an AST will rewrite methods to use a transaction template, but the net effect is basically the same. If there are no annotations, then unless you have static transactional = false, then the service is transactional and a Spring proxy is created (the same as if you had included the Spring @Transactional annotation at the class level). static transactional = true is never needed since it's the default; the only way for a service to be completely non-transactional is to include static transactional = false and have no @Transactional annotations.

One thing that could be happening is that the underlying table might not be transactional. Newer versions of MySQL default to InnoDB as the table type, but before 5.5 the default was MyISAM. Grails auto-detects the database and registers a Hibernate Dialect for you, and this works well in most cases except for MySQL + MyISAM. To ensure that you always use InnoDB, specify an appropriate Dialect in DataSource.groovy, e.g.

dataSource {
   dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}

This will only help with new tables that are created by Hibernate going forward. Be sure to convert any existing MyISAM tables to InnoDB (although in this case that wouldn't be needed since you're using create-drop).

like image 24
Burt Beckwith Avatar answered Sep 23 '22 13:09

Burt Beckwith