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