Grails有一个用于单个数据库的多租户插件和一个用于多个数据库的多租户插件,但不再支持/维护用于多个数据库的插件。有没有什么方法可以将Spring或Hibernate本身用于多租户多数据库Grails应用程序?
答案 0 :(得分:9)
您可以使用此处所述的Hibernate多租户http://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html
或强>
你也可以考虑Spring的AbstractRoutingDataSource
一般的想法是路由DataSource充当中介 - 可以根据查找键在运行时动态确定“真正的”数据源。
https://spring.io/blog/2007/01/23/dynamic-datasource-routing/
你可以找到一个较新的post,给出一个示例性的hibernate用法,你可以在以下两个片段中找到解决方案的要点
public class MyRoutingDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
String language = LocaleContextHolder.getLocale().getLanguage();
System.out.println("Language obtained: "+ language);
return language;
}
}
返回值将用作数据源的鉴别器,以下配置设置映射
<bean id="dataSource" class="com.howtodoinjava.controller.MyRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="en" value-ref="concreteDataSourceOne"/>
<entry key="es" value-ref="concreteDataSourceTwo"/>
</map>
</property>
</bean>
答案 1 :(得分:2)
在我们的例子中,我们使用LocalContainerEntityManagerFactoryBean来创建multiTenantMySQLProvider。
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="packagesToScan" value="domain"/>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
<entry key="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.multiTenancy" value="SCHEMA" />
<entry key="hibernate.multi_tenant_connection_provider" value-ref="mySQLMultiTenantConnectionProvider" />
<entry key="hibernate.tenant_identifier_resolver" value-ref="tenantIdentifierResolver" />
</map>
</property>
</bean>
<bean id="tenantService"
class="multitenancy.service.impl.TenantServiceImpl">
<property name="defaultTenantId" value="${multitenancy.defaultTenantId}" />
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
<bean id="connectionProvider"
class="multitenancy.hibernate.ConnectionProviderImpl" lazy-init="false">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="mySQLMultiTenantConnectionProvider"
class="multitenancy.hibernate.MySQLMultiTenantConnectionProviderImpl" lazy-init="false">
<property name="connectionProvider" ref="connectionProvider" />
<property name="tenantIdentifierForAny" value="${multitenancy.tenantIdentifierForAny}" />
<property name="schemaPrefix" value="${multitenancy.schemaPrefix}" />
</bean>
<bean id="tenantIdentifierResolver"
class="multitenancy.hibernate.TenantIdentifierResolverImpl" lazy-init="false">
<property name="tenantService" ref="tenantService" />
</bean>
<bean id="tenantIdentifierSchedulerResolver"
class="security.impl.TenantIdentifierSchedulerResolverImpl" lazy-init="false">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
这里是MySQLMultiTenantConnectionProviderImpl的实现
public class MySQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, ServiceRegistryAwareService, Stoppable {
private static final Logger LOGGER = LoggerFactory.getLogger(MySQLMultiTenantConnectionProviderImpl.class);
@Setter
private ConnectionProvider connectionProvider;
@Setter
private String tenantIdentifierForAny;
@Setter
private String schemaPrefix;
@Override
public Connection getAnyConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connectionProvider.closeConnection( connection );
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
String schema = schemaPrefix + tenantIdentifier;
try {
LOGGER.debug("setting schema in DB Connection : {}" , schema);
connection.createStatement().execute( "USE " + schema );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + schema + "]", e
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + tenantIdentifierForAny );
}
catch ( SQLException e ) {
LOGGER.error(" error on releaseConnection. The connection will be not closed. SQLException : {}" , e);
// on error, throw an exception to make sure the connection is not returned to the pool.
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e
);
}
// I follow the hibernate recommendation and we don't return the connetion to the pool.
connectionProvider.closeConnection( connection );
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
@Override
public void stop() {
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) ||
MultiTenantConnectionProvider.class.equals( unwrapType ) ||
AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType );
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
throw new UnknownUnwrapTypeException( unwrapType );
}
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
}
}
答案 2 :(得分:1)
这就是我如何使用带有SCHEMA方法的hibernate多租户。可能会对你有所帮助。
的applicationContext.xml
...
<bean id="multiTenantConnectionProvider" class="org.myapp.MyAppMultiTenantConnectionProvider"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="org.myapp.entities"/>
<property name="multiTenantConnectionProvider" ref="multiTenantConnectionProvider"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.multiTenancy">SCHEMA</prop>
<prop key="hibernate.tenant_identifier_resolver">org.myapp.MyAppTenantIdentifierResolver</prop>
...
</props>
</property>
</bean>
...
MyAppMultiTenantConnectionProvider.java
public class MyAppMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
//This is where your tenant resolving logic will be implemented
return MyMultitenantConnectionPool.getConnection(tenantIdentifier);
}
}
MyAppTenantIdentifierResolver.java
public class MyAppTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
/*
This is where you determine which tenant to use.
In this app SpringSecurity used for this purpose.
TenantUser class extends org.springframework.security.core.userdetails.User with tenant information.
*/
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) return "";
if (authentication.getPrincipal() instanceof TenantUser) {
TenantUser user = (TenantUser) authentication.getPrincipal();
return user.getTenant();
} else return "";
}
@Override
public boolean validateExistingCurrentSessions() {
return false;
}
}