Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grails 3.1 Transactional Behavior & Documentation Appear Incorrect #9785

Closed
ctoestreich opened this issue Mar 9, 2016 · 31 comments
Closed
Assignees
Milestone

Comments

@ctoestreich
Copy link
Contributor

The current documentation for grails 3.1.x states that transactions are disabled by default for services and can be enabled using grails.spring.transactionManagement: true in yaml. However the property appears to actually be grails.spring.transactionManagement.proxies AND it appears to be true by default as seen here https://github.com/grails/grails-core/search?utf8=%E2%9C%93&q=SPRING_TRANSACTION_MANAGEMENT

We were able to get the default for transactions to be off by setting grails.spring.transactionManagement.proxies: false in our yaml. After doing this we were seeing the correct number of transactions only for those methods annotated with @transactional.

Note the incorrect property setting here https://grails.github.io/grails-doc/latest/guide/services.html

Line where property is defaulting for all services to true:

final boolean springTransactionManagement = config.getProperty(Settings.SPRING_TRANSACTION_MANAGEMENT, Boolean.class, true)

screen shot 2016-03-09 at 12 23 39 pm

This appears to go back to DefaultGrailsServiceClass.java where a service with no annotation or static transactional is being wired as transactional true due to transactional = tmpTransactional == null

 public DefaultGrailsServiceClass(Class<?> clazz) {
        super(clazz, SERVICE);

        Object tmpTransactional = getPropertyOrStaticPropertyOrFieldValue(TRANSACTIONAL, Boolean.class);
        transactional = tmpTransactional == null || tmpTransactional.equals(Boolean.TRUE);
    }

then springTransactionManagement is defaulting to true via config.getProperty(Settings.SPRING_TRANSACTION_MANAGEMENT, Boolean.class, true)

Then the logic fires at shouldCreateTransactionalProxy and the evaluation for the logical condition returns true because all these evaluate to true where ALL services with no static transactional are serviceClass.transactional = true have no annotation !AnnotationUtils.findAnnotation(javaClass, grails.transaction.Transactional) = true have no spring annotation !AnnotationUtils.findAnnotation(javaClass, Transactional) = true and no method has spring of grails transactional !javaClass.methods.any { Method m -> AnnotationUtils.findAnnotation(m, Transactional) != null || AnnotationUtils.findAnnotation(m, grails.transaction.Transactional) != null} = true

This is the existing block:

serviceClass.transactional &&
              !AnnotationUtils.findAnnotation(javaClass, grails.transaction.Transactional) &&
              !AnnotationUtils.findAnnotation(javaClass, Transactional) &&
                 !javaClass.methods.any { Method m -> AnnotationUtils.findAnnotation(m, Transactional) != null ||
                                                        AnnotationUtils.findAnnotation(m, grails.transaction.Transactional) != null}

Due to how it checks for transactions simply using grails.spring.transactionManagement.proxies: false works, however this is incorrect in the documentation and the default value is currently true for this value when not provided.

Newly generated projects come ouf the gate with

grails:
    spring:
        transactionManagement: false

but this doesn't actually appear to affect the transactional behavior.

@ctoestreich
Copy link
Contributor Author

Will be sending pull request following discussion with @jeffbrown using the logic as

if(service has @grails.transaction.Transactional) {
   make it transactional with the AST transformation
} else if(service has org.springframework.transaction.annotation.Transactional ||
          service has transactional=true) {
   make it transactional with a proxy
} else if(grails.spring.transactionManagement.proxies=true &&
          !(service has transactional=false)){
   make it transactional with a proxy
} else {
   not transactional
}

@osscontributor
Copy link
Member

I think the logic quoted above is reasonable. Feedback is welcome.

@osscontributor
Copy link
Member

I think the proposed change...

serviceClass.transactional && 
        (AnnotationUtils.findAnnotation(javaClass, grails.transaction.Transactional) != null || 
                AnnotationUtils.findAnnotation(javaClass, Transactional) != null || 
                javaClass.methods.any { Method m -> 
                    AnnotationUtils.findAnnotation(m, Transactional) != null || AnnotationUtils.findAnnotation(m, grails.transaction.Transactional) != null 
                }
        )

...will yield the wrong behavior. I think that will create the proxy when the annotation is present.

@ctoestreich
Copy link
Contributor Author

Yeah that is wrong. Understanding how the static property and annotation work has cleared that up.

@osscontributor
Copy link
Member

Just to restate what was described in Slack, I think the only issue here is the config property name and its default value. Is that correct?

@ctoestreich
Copy link
Contributor Author

Yes, two line change minus tests.

@osscontributor
Copy link
Member

I am wondering if we should leave the default config property value as is for 3.1.x and only change it for 3.2 since it is a real change in behavior that could be problematic.

@osscontributor
Copy link
Member

I would like @graemerocher's input before we merge any PRs. He is on vacation this week.

@ctoestreich
Copy link
Contributor Author

I can pull against master in that case. I can PR again against 3.1.x later.

@osscontributor
Copy link
Member

I think that is fine. If you make it behave as described in the logic you quoted above and make that into a PR against master, then we can take it from there.

Thanks for all of the help.

@ctoestreich
Copy link
Contributor Author

The code change was easy... the tests are proving to be a !@#$.

@ctoestreich
Copy link
Contributor Author

It is actually the datasourceName in the DefaultGrailsServiceClass that is causing some issues when calling getDatasource that is proving difficult.

@osscontributor
Copy link
Member

It is actually the datasourceName in the DefaultGrailsServiceClass that is causing some issues when calling getDatasource that is proving difficult.

Is that relevant to the transactional behavior and the docs being out of sync?

@ctoestreich
Copy link
Contributor Author

I have a work around but the data source method used to work one way when the static property for transactional was null or true. Now if we only detect true it changes the behavior of the ultimate name of the datasource. It would be bad for to inspect annotations in core so I will have to reevaluate and perhaps set transactional during service wiring when annotations present and proxy intended. Will post code here in a bit.

@ctoestreich
Copy link
Contributor Author

Given the attachment of the datasource name to the service artifact, and it's apparent dependency to knowing the transactionality of the service, even if removing the static property this will probably be necessary.

@ctoestreich
Copy link
Contributor Author

I probably need to digest how the datasource, the service artifact and transactions all play together a bit more.

@ctoestreich
Copy link
Contributor Author

The logic that I landed on is as follows

if(service has @grails.transaction.Transactional) {
   make it transactional with the AST transformation
} else if((service has org.springframework.transaction.annotation.Transactional ||
          service has transactional=true) && grails.spring.transactionManagement.proxies=true) {
   make it transactional with a proxy
} else {
   not transactional
}

@osscontributor
Copy link
Member

if(service has @grails.transaction.Transactional) {
   make it transactional with the AST transformation
} else if((service has org.springframework.transaction.annotation.Transactional ||
          service has transactional=true) && grails.spring.transactionManagement.proxies=true) {
   make it transactional with a proxy
} else {
   not transactional
}

I think that would mean that if grails.spring.transactionManagement.proxies==false and the service has org.springframework.transaction.annotation.Transactional that no proxy will be generated. I think that is the wrong thing to do, but more importantly that would be a difficult thing to do because I think Spring is going to create the proxy automatically because of the annotation.

@ctoestreich
Copy link
Contributor Author

We have observed in 3.1.0 when setting grails.spring.transactionManagement.proxies: false that our transaction count goes way down and the speed goes way up. I understand your concern. I think there needs to be clarity on what the real intent of the config value is. Is it to enable property interrogation of proxy wiring or to disable spring proxy wiring.

@osscontributor
Copy link
Member

Is it to enable property interrogation of proxy wiring or to disable spring proxy wiring.

Even if you wanted it to prevent proxies for classes marked with org.springframework.transaction.annotation.Transactional, how would you go about making that happen?

@osscontributor
Copy link
Member

Is the proposal to remove (or maybe just not to add) the annotation driven transaction management post processor stuff if the config setting is set to false?

@ctoestreich
Copy link
Contributor Author

I didn't run through the full spring wiring scenario but the code in the PR will skip wiring the TypeSpecifyableTransactionProxyFactoryBean if the comfig property is set to false. It would appear, based on the tests, that this works on the surface. But I defer to you, having more experience in spring code than I, if that isn't sufficient.

@osscontributor
Copy link
Member

I don't think skipping the wiring of a TypeSpecifyableTransactionProxyFactoryBean is enough to prevent Spring from doing its normal thing with org.springframework.transaction.annotation.Transactional. I think we were being explicit about using TypeSpecifyableTransactionProxyFactoryBean because that is a way to tell Spring to create a proxy for a class that isn't marked with the annotation.

@osscontributor
Copy link
Member

I think it might work if we omitted adding the post processor but I am not sure that is the right thing. Maybe it is.

My thinking is that if the author of a class explicitly marks a class with org.springframework.transaction.annotation.Transactional that they are being explicit in saying "I want to use the Spring proxy".

@ctoestreich
Copy link
Contributor Author

I think the best course is the simplest and to adjust the config value to simply toggle the support for the static property.

@ctoestreich
Copy link
Contributor Author

Will update PR tomorrow am.

@osscontributor
Copy link
Member

The code at

final boolean springTransactionManagement = config.getProperty(Settings.SPRING_TRANSACTION_MANAGEMENT, Boolean.class, true)
looks like this...

final boolean springTransactionManagement = config.getProperty(Settings.SPRING_TRANSACTION_MANAGEMENT, Boolean.class, true)

if(springTransactionManagement) {
    xmlns tx:"http://www.springframework.org/schema/tx"
    tx.'annotation-driven'('transaction-manager':'transactionManager')
}

That appears to be written with the intent of not registering the annotation driven transaction manager contraption if the config setting is set to false. I think that ignoring the Spring annotation altogether when the config setting is false is potentially problematic. Maybe I am the minority with that concern though.

@osscontributor
Copy link
Member

An issue I see is that as the author of a plugin I may be explicit in saying I want the Spring proxy. I do that by using the Spring annotation. If that plugin is used in an app that sets the proxy setting to false, bad things could happen. So far I think I am outnumbered though.

@osscontributor
Copy link
Member

The more I think about this, I am backing away from my own position above. I think the intent may be to disable the proxying altogether when setting the config setting to false and the motivation for doing that may be to optimize startup time.

@ctoestreich
Copy link
Contributor Author

We no longer use any spring annotations in favor of the grails annotation and disabling the default transactionality via the config flag has given us a HUGE boost in performance. I will defer this issue to you and your minions, but wanted to throw that out there.

@osscontributor
Copy link
Member

We no longer use any spring annotations in favor of the grails annotation and disabling the default transactionality via the config flag has given us a HUGE boost in performance. I will defer this issue to you and your minions, but wanted to throw that out there.

That all makes sense to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants