具有多个数据库的 Grails Multi-Tenancy (每个租户一个)2.4.0

标签 grails multi-tenant

我似乎找不到任何关于 Grails Multitenancy with Multiple Databases(每个租户一个)的插件或示例。我正在使用 grails 2.4.0 。谁能帮我?

最佳答案

在我的应用程序中,我需要能够访问多个数据库,并且在启动时我没有到数据库的连接字符串,因此我无法将它们预配置为数据源。

我使用的策略如下:

1) 实现一个存储数据源集合的抽象路由数据源。它还用DataSource{}注册了一个全局闭包,一个接受的一些参数和代码块。参数是数据源的名称和连接信息。在 withDataSource 方法中,它要么创建一个新的 DataSource 并使用其键将其保存到 map 中,要么使用 map 中保存的内容。

2) 在我的客户端代码中,我将使用 withDataSource 闭包来访问我需要的任何数据库。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 
import org.springframework.context.ApplicationContextAware
import org.springframework.beans.factory.DisposableBean
import org.springframework.beans.factory.InitializingBean
import org.springframework.context.ApplicationContext 
import javax.sql.DataSource 
import org.springframework.jdbc.datasource.DriverManagerDataSource 
import org.springframework.jdbc.datasource.lookup.DataSourceLookup
import org.springframework.jdbc.datasource.SimpleDriverDataSource

class SwitchableDataSource 
              extends AbstractRoutingDataSource 
              implements ApplicationContextAware, 
                         InitializingBean,
                         DisposableBean
                         { 

  ApplicationContext applicationContext 

  Map _targetDataSources
  private ThreadLocal<String> currentDataSource = new ThreadLocal<String>() 

  void afterPropertiesSet(){
    super.afterPropertiesSet()
    // register global closure for setting the current datasource
    // ... should probably be restricted to services and controllers.
    Object.metaClass.withDataSource = this.&executeWithDataSource
  }

  public synchronized def executeWithDataSource(def params, def method){

      // if datasource doesn't exist, register new one in application context
      // set a threadlocal key for the current datasource key
      def dataSourceName = ""
      def key = params.clientId.toString()

      if( !_targetDataSources.containsKey(key) ){

        dataSourceName = "dataSource_${params.clientId}".toString()
        applicationContext.registerSingleton(dataSourceName,
              //org.apache.commons.dbcp.BasicDataSource,
              org.springframework.jdbc.datasource.DriverManagerDataSource,
              new org.springframework.beans.MutablePropertyValues(
              [
                driverClassName : 'com.mysql.jdbc.Driver',
                username : params.username.toString(),
                password : params.password.toString(),
                url : params.connectionString.toString()
              ]
            )
        )

        // add datasource to map
        _targetDataSources[key] = applicationContext.getBean(dataSourceName)
        targetDataSources = _targetDataSources
        super.afterPropertiesSet() // AbstractRoutingDataSource is lame... it converts 'targetDataSource' into 'resolvedDataSource' in afterPropertiesSet()
      }

      // set the thread local variable for the current datasource
      currentDataSource.set(params.clientId.toString())

      // call the closure that was passed in containing some data access code
      // and return whatever it returns
      return method()
  }

  void destroy(){
    // remove global closure
    Object.metaClass.withDataSource = null // may need to throw a missing method exception
  }

  protected Object determineCurrentLookupKey() { 
    (currentDataSource.get()?:"DEFAULT_DATASOURCE").toString()
  }
}

2) 在客户端代码中,进行如下调用:
withDataSource( [ clientId: client.id, // client.id was my key
        connectionString: jdbcConnection.url,
        username: jdbcConnection.username,
        password: jdbcConnection.password ] ) { 

        SomeDomainObject.list()
        SomeDomainObject.delete();
    }

3)记得在你的resources.groovy中用spring注册你的路由数据源。请注意,它分配了一个默认数据源,因此初始数据源列表不为空。
// Place your Spring DSL code here
beans = {

  dataSource_defaultClientDb(org.apache.commons.dbcp.BasicDataSource) { bean ->
    bean.singleton = true
    driverClassName = 'com.mysql.jdbc.Driver'
    username = '${defaultClientDB_username}'
    password = '${defaultClientDB_password}'
    url = '${defaultClientDB_url}'
  }

  dataSource_clientdb(SwitchableDataSource){ bean ->
    bean.singleton = true 
    _targetDataSources = ["DEFAULT_DATASOURCE":ref("dataSource_defaultClientDb")]
    targetDataSources = _targetDataSources
  }

  lobHandlerDetector_clientdb(org.springframework.jdbc.support.lob.DefaultLobHandler)

}

对于您的应用程序,您可能不需要像我使用的那样使用 withDataSource 方法 - 您只需要注册与可切换数据源的连接并告诉它如何查找它应该使用的数据源,例如您可以分配一些 ID每个请求上的线程本地数据和可切换数据源可以使用相同的线程本地值来确定要使用哪个数据源。

我希望这至少能让你朝着正确的方向前进。

关于具有多个数据库的 Grails Multi-Tenancy (每个租户一个)2.4.0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33496684/

相关文章:

grails - 国际化 Grails 应用程序中的模型

tomcat - 如何将日志从 grails 应用程序发送到单独的文件

spring - 使用 Spring MVC 和 Hibernate 4.2.0.Final 的 Multi-Tenancy Web 应用程序

azure - Azure AD Multi-Tenancy SaaS 应用程序的租户特定主页 URL

mysql - MySQL 应用程序中的安全 Multi-Tenancy

php - 使用多个数据库运行 laravel 队列

unit-testing - 如何在 Grails/Spock 中模拟 HttpServletRequest 的 BufferedReader 负载

sql - 来自Grails中的unicode的SQL语法错误(度数为\u00B0)

session - 如何在grails中使用 session

c# - 在 VS 中的本地网络服务器上测试子域