Grails services are not transactional?
Asked Answered
L

3

5

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.

Launder answered 26/2, 2015 at 14:5 Comment(1)
There isn't enough information here. How are these services being used, are you creating your own instance or making an instance variable that Grails can inject a Spring-managed bean into? Can you reproduce this in a small example running against H2 (in order to exclude possible issues from how your mysql instance is set up)?Wager
L
12

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.

Launder answered 26/2, 2015 at 15:57 Comment(0)
R
3

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).

Refluent answered 26/2, 2015 at 15:55 Comment(0)
G
0

For anyone coming to this question after years of asking, since Grails 3.1 this has been changed. Today at Grails 5.2.5 Services are NOT transactional by default. The "static transactional" property does nothing anymore. You can make a service transactional by annotating it with @Transactional

More has been said about this change here: https://docs.grails.org/latest/guide/services.html#declarativeTransactions

Geaghan answered 16/2, 2023 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.