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

Odd GORM transaction behavior after upgrading from Grails 2.5 to Grails 3.3.1 #62

Closed
mlim1972 opened this issue Nov 9, 2017 · 2 comments

Comments

@mlim1972
Copy link

mlim1972 commented Nov 9, 2017

Some code that was working in 2.5.x is currently not working in 3.3.1. This particular issue is related to GORM and transactions participation (we think). Basically, I have two domain classes where 1 of them (Pipeline) has many of the other (Rule). I have two controllers where I can save a Pipeline first. Then, I call the RuleController to save a rule and add to the pipeline in a Service. However, I want to do some queries to find the Rule after I added it to the pipeline. (Notice this is a simplified example of querying for the Rule on other Pipelines.) However, I noticed that using CreateCriteria, ExecuteQuery, Where, will not get any data unless I flush the save of the pipeline. Is this the right behavior? We used to do this in Grails 2.5.x and it was yielding the data w/o flushing the save.

We are using mySQL as the persistent storage...

Here is the code to replicate the issue

Domain Class
Rule

class Rule {
    String name
    static constraints = {
    }
}

Pipeline

class Pipeline {
    String name
    static hasMany = [rules: Rule]
    static constraints = {
    }
}

Controllers
PipelineController

@Artefact("Controller")
class PipelineController extends RestfulController{
    static responseFormats = ['json', 'xml']
    PipelineController(){
        super(Pipeline)
    }
}

RuleController

@Artefact("Controller")
class RuleController extends RestfulController{
    static responseFormats = ['json', 'xml']
    def pipelineService

    RuleController(){
        super(Rule)
    }

    @Override
    def save() {
        def apiParams = request.SON
        def instance = pipelineService.addToPipeline(apiParams)
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [classMessageArg, instance.id])
                redirect instance
            }
            '*' {
                response.addHeader(HttpHeaders.LOCATION,
                        grailsLinkGenerator.link( resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
                                namespace: hasProperty('namespace') ? this.namespace : null ))
                respond instance, [status: CREATED, view:'show']
            }
        }
    }
}

Service
PipelineService

@Transactional
class PipelineService {
    def addToPipeline(params) {
        def rule = new Rule(name: params.name)
        rule.save()
        def ruleID = rule.id
        Pipeline pipeline = Pipeline.get(params.pipelineId)
        pipeline.addToRules(rule)

        // commenting out save is the same as explicit save()
        pipeline.save()     // this will not allow createCriteria, where, or executeQuery to find pipeline for the just inserted rule
        //pipeline.save(flush:true)     // this will let all queries work

        // this gets the result no matter if we flush or not
        def results = Pipeline.list().findAll{rule in it.rules}

        // None of the queries below find anything unless save is flushed
        //

        // withTransactions or withSession still won't find unless flush is set
        //Pipeline.withTransaction{
        def c = Pipeline.createCriteria()
        def results2 = c.list {
            rules{
                eq("id",ruleID)
            }
        }
        //}

        def query = "select distinct p from Pipeline p inner join p.rules rules where rules in (:rule) "
        def results3 = []
        try{
            results3 = Pipeline.executeQuery(query,[rule:rule])
        }catch(e){
            e.printStackTrace()
        }

        def results4 = Pipeline.where {
            rules.id == ruleID
        }.list()

        println "results: ${results}"
        println "results2: ${results2}"
        println "results3: ${results3}"
        println "results3: ${results4}"

        return rule
    }
}

UrlMappings

        "/api/rules"(resources:'Rule')
        "/api/pipelines"(resources:'Pipeline')

Steps to reproduce:

  1. Create a new pipeline by POSTing to /api/pipelines
{"name":"Pipeline1"}
  1. Create a new rule to add to the newly created pipeline (ID 1) by POSTing to /api/rules
{"name":"rule1", "pipelineId":1} 
  1. The console should show the pipeline for all results but it is not finding them except for 1 of the queries. If we flush the save for the pipeline, then all queries will find the record in question. See comments in the Service class.
@graemerocher
Copy link
Member

See http://docs.grails.org/latest/guide/upgrading.html section "Flush Mode now COMMIT by Default"

@vishwanath-openprise
Copy link

@graemerocher within same transaction, session, if an object is updated and saved (irrespective whether it is flushed or not) if we query for the same object we should be able to get the dirty instance back. Right?

My understanding:

  1. For get, load within the same transaction: We look at session first, if not found then second level cache if not found then hit DB to retrieve it.
  2. For queries: We hit DB first and retrieve the list and then include/update matching objects from second level cache and then from current session.

I validated this understanding with spring-hibernate(5) project and it looks to be valid.
In fact I tried with grails 3.2.9 - and works there too. But not in grails 3.3.1

Does grails, gorm work differently?

Spring-hiberbnate:
User n = new User();
n.setName(name);
n.setEmail(email);
Session session = sessionFactory.getCurrentSession();
session.save(user);
Criteria c = session.createCriteria(User.class);
c.add(Restrictions.eq("name", user.getName()));
List results = c.list();
System.out.println("Size: " + results.size());

grails-gorm (Works in 3.2.9, but not in 3.3.1):
TestObject o = new TestObject()
o.name = "name_here"
o.description = "desc_here"
o.save()
println "START createCriteria--------------"
def c = TestObject.createCriteria()
def results = c.list {
eq("name", o.name)
}
results?.each {
println it.name + " " + it.description
}
println "END createCriteria--------------"

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

No branches or pull requests

3 participants