我似乎找不到任何关于Grails Multitenancy with Multiple Databases的插件或示例(每个租户一个)。我使用的是grails 2.4.0。任何人都可以帮助我吗?
答案 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可以使用相同的线程本地值来确定要使用的数据源。
我希望这至少会让你朝着正确的方向前进。