带有多个数据库的Grails多租户(每个租户一个),2.4.0

时间:2015-11-03 10:25:10

标签: grails multi-tenant

我似乎找不到任何关于Grails Multitenancy with Multiple Databases的插件或示例(每个租户一个)。我使用的是grails 2.4.0。任何人都可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

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

我使用的策略如下:

1)实现一个abstractroutingdatasource,它存储一组数据源。它还注册了一个全局闭包withDataSource {},接受了一些参数和代码块。参数是数据源的名称和连接信息。在withDataSource方法中,它创建一个新的DataSource并使用其键将其保存到地图中,或者使用地图上保存的内容。

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

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<h3 class="alert alert-success">Example 1: Using Bootstrap classes</h3>

<nav class="navbar navbar-inverse navbar-static-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>

      </button><a class="navbar-brand" href="#">Brand</a>

      <p class="navbar-text navbar-left">This is navbar-text <a href="#" class="navbar-link">and this is a navbar-link</a>

      </p>
    </div>
    <div class="collapse navbar-collapse" id="bs-1">
      <p class="navbar-text">This is navbar-text <a href="#" class="navbar-link">and this is a navbar-link</a>

      </p>
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link</a>

        </li>
      </ul>
    </div>
  </div>
</nav>
<hr>
<h3 class="alert alert-info">Example 2: Not using Bootstrap classes</h3>

<nav class="navbar navbar-default navbar-static-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-2" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>

      </button><a class="navbar-brand" href="#">Brand</a>

      <p class="not-navbar-text">This is NOT navbar-text <a href="#" class="not-navbar-link">and this is NOT navbar-link</a>

      </p>
    </div>
    <div class="collapse navbar-collapse" id="bs-2">
      <p class="not-navbar-text">This is NOT navbar-text <a href="#" class="not-navbar-link">and this is NOT navbar-link</a>

      </p>
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link</a>

        </li>
      </ul>
    </div>
  </div>
</nav>
<hr>
<h3 class="alert alert-warning">Example 3: You example</h3>

<nav class="navbar navbar-default navbar-static-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-3" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>

      </button><a class="navbar-brand" href="#">Brand</a>

    </div>
    <div class="collapse navbar-collapse" id="bs-3">
      <ul class="nav navbar-nav">
        <li>
          <div class="navbar-text"><a href="#" class="navbar-link">Follow us!</a>

          </div>
        </li>
        <li><a href="#">Follow us!</a>

        </li>
      </ul>
    </div>
  </div>
</nav>
<hr>
<h3 class="alert alert-success">Example 4: Navbar-Link</h3>
<nav class="navbar navbar-inverse navbar-static-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>

      </button><a class="navbar-brand" href="#">Brand</a>

      <p class="navbar-text"> <a href="#">Normal Link</a>  <a href="#" class="navbar-link">Navbar-link</a>

      </p>
    </div>
    <div class="collapse navbar-collapse" id="bs-1">
      <ul class="nav navbar-nav">
        <li><a href="#">Normal Link</a>

        </li>
        <li><a href="#" class="navbar-link">Navbar-link</a>

        </li>
      </ul>
    </div>
  </div>
</nav>

2)在客户端代码中,按如下方式进行调用:

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()
  }
}

3)记得在resources.groovy中使用spring注册路由数据源。请注意,它分配了一个默认数据源,因此初始数据源列表不为空。

withDataSource( [ clientId: client.id, // client.id was my key
        connectionString: jdbcConnection.url,
        username: jdbcConnection.username,
        password: jdbcConnection.password ] ) { 

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

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

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