0

I'm trying to run sql-code from inside an ordinary Groovy class (no service). In Grails 2 I could access a datasource by doing this:

 public GroovyClass() {
    def ctx = ServletContextHolder.servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
    def dataSource = ctx.getBean('dataSource')
    sql = new Sql(dataSource)
 }

After migrating to Grails 3.3.8 the code no longer works. What is the correct method in Grails 3.3.8?

ascu
  • 1,029
  • 1
  • 19
  • 33
  • The accepted answer below is a bad idea. I would not advocate for doing that. The right answer requires knowing more about the context from which you are interacting with this groovy class. Are you interacting with it from a controller? A service? BootStrap? Something else? – Jeff Scott Brown Aug 23 '18 at 15:30
  • @JeffScottBrown From a Grails service – ascu Aug 24 '18 at 06:57

2 Answers2

2

The "Hollywood Principle" says "Don't call us, we will call you" and that is what dependency injection is about. In your case, don't go get the dataSource, have the dataSource given to you.

There are a number of ways to do that. See the project at https://github.com/jeffbrown/asdudemo.

https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/FirstGroovyClass.groovy

// src/main/groovy/ascudemo/FirstGroovyClass.groovy
package ascudemo.helpers

import groovy.sql.Sql
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.InitializingBean

import javax.sql.DataSource

@Slf4j
class FirstGroovyClass implements InitializingBean {
    DataSource dataSource
    Sql sql

    void logSomeInfo() {
        // both have been initialized
        log.debug "dataSource: ${dataSource}"
        log.debug "sql: ${sql}"
    }

    @Override
    void afterPropertiesSet() throws Exception {
        sql = new Sql(dataSource)
    }
}

https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/SecondGroovyClass.groovy

// src/main/groovy/ascudemo/SecondGroovyClass.groovy
package ascudemo.helpers

import groovy.sql.Sql
import groovy.util.logging.Slf4j

import javax.annotation.PostConstruct
import javax.sql.DataSource

@Slf4j
class SecondGroovyClass {
    DataSource dataSource
    Sql sql

    void logSomeInfo() {
        // both have been initialized
        log.debug "dataSource: ${dataSource}"
        log.debug "sql: ${sql}"
    }

    @PostConstruct
    void initSql() throws Exception {
        sql = new Sql(dataSource)
    }
}

https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/ThirdGroovyClass.groovy

// src/main/groovy/ascudemo/SecondGroovyClass.groovy
package ascudemo.helpers

import groovy.sql.Sql
import groovy.util.logging.Slf4j
import org.springframework.beans.BeansException
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

import javax.sql.DataSource

@Slf4j
class ThirdGroovyClass implements ApplicationContextAware {
    Sql sql

    void logSomeInfo() {
        // sql been initialized
        log.debug "sql: ${sql}"
    }

    @Override
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        DataSource dataSource = applicationContext.getBean('dataSource', DataSource)
        sql = new Sql(dataSource)
    }
}

https://github.com/jeffbrown/asdudemo/blob/master/grails-app/controllers/ascudemo/DemoController.groovy

// grails-app/controllers/ascudemo/DemoController.groovy
package ascudemo

class DemoController {

    SomeService someService

    def index() {
        someService.logSomeInfo()
        render 'Success'
    }
}

https://github.com/jeffbrown/asdudemo/blob/master/grails-app/services/ascudemo/SomeService.groovy

// grails-app/services/ascudemo/SomeService.groovy
package ascudemo

import ascudemo.helpers.FirstGroovyClass
import ascudemo.helpers.SecondGroovyClass
import ascudemo.helpers.ThirdGroovyClass

class SomeService {

    FirstGroovyClass firstGroovyBean
    SecondGroovyClass secondGroovyBean
    ThirdGroovyClass thirdGroovyBean

    def logSomeInfo() {
        firstGroovyBean.logSomeInfo()
        secondGroovyBean.logSomeInfo()
        thirdGroovyBean.logSomeInfo()
    }
}

https://github.com/jeffbrown/asdudemo/blob/master/grails-app/conf/spring/resources.groovy

// grails-app/conf/spring/resources.groovy
import ascudemo.helpers.FirstGroovyClass
import ascudemo.helpers.SecondGroovyClass
import ascudemo.helpers.ThirdGroovyClass

beans = {
    // demonstrates one approach
    firstGroovyBean(FirstGroovyClass) { bean ->
        bean.autowire = 'byName'
    }

    // demonstrates another approach
    secondGroovyBean(SecondGroovyClass) { bean ->
        bean.autowire = 'byName'
    }

    // demonstrates a third approach
    thirdGroovyBean ThirdGroovyClass
}

Run the app and send a request to http://localhost:8080/demo/ and that will verify that all 3 approaches worked.

There are other ways to do this but I hope that one of the above will be helpful to you.

Best of luck!

Jeff Scott Brown
  • 26,804
  • 2
  • 30
  • 47
  • That may look more complicated than it is, in part because there are 3 different solutions shown above. All you are really doing is adding an instance of your class to the Spring application context (in `resources.groovy`) so it can be subjected to DI and then injecting that bean wherever you want to use it. In the example above that is `SomeService`. – Jeff Scott Brown Aug 24 '18 at 14:50
  • 1
    Appreciate the thorough answer, very helpful and educational. – ascu Aug 27 '18 at 06:20
0

I just tested this and it seems to give the datasource

def ds = Holders.grailsApplication.mainContext.getBean('dataSource')
println "DataSource: ${ds}"  ---> DataSource: org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@5e91ade8

Didn't try any operations on it, but that looks right.

Trebla
  • 1,164
  • 1
  • 13
  • 28