我正在尝试设置一个多租户Web应用程序,同时具有(理想情况下)数据库分离和模式分离方法的可能性。虽然我将从Schema分离开始。我们目前正在使用:
我主要关注this thread(因为@Transactional
的解决方案)。但是我在实施MultiTenantContextConnectionProvider
时迷失了方向。还有this similar question在这里问过SO,但有些方面我无法弄清楚:
1)连接池会发生什么?我的意思是,它是由Spring还是Hibernate管理的?我想用ConnectionProviderBuilder
- 或者按照建议 - 任何实现,Hibernate都是管理它的人
2)Spring不管理连接池是一种好方法吗?或者Spring是否有可能对其进行管理?
3)这是未来实现数据库和模式分离的正确途径吗?
完全赞赏任何评论或说明。
应用context.xml中
<beans>
...
<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource" ref="c3p0DataSource" />
</bean>
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="org.postgresql.Driver" />
... other C3P0 related config
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="com.webapp.domain.model" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.default_schema">public</prop>
<prop key="hibernate.multiTenancy">SCHEMA</prop>
<prop key="hibernate.tenant_identifier_resolver">com.webapp.persistence.utility.CurrentTenantContextIdentifierResolver</prop>
<prop key="hibernate.multi_tenant_connection_provider">com.webapp.persistence.utility.MultiTenantContextConnectionProvider</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="autodetectDataSource" value="false" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
...
</beans>
CurrentTenantContextIdentifierResolver.java
public class CurrentTenantContextIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
return CurrentTenantIdentifier; // e.g.: public, tid130, tid456, ...
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
MultiTenantContextConnectionProvider.java
public class MultiTenantContextConnectionProvider extends AbstractMultiTenantConnectionProvider {
// Do I need this and its configuratrion?
//private C3P0ConnectionProvider connectionProvider = null;
@Override
public ConnectionProvider getAnyConnectionProvider() {
// the main question is here.
}
@Override
public ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
// and of course here.
}
}
修改
关于@ ben75的the answer:
这是MultiTenantContextConnectionProvider
的新实现。它不再延伸AbstractMultiTenantConnectionProvider
。而是实现MultiTenantConnectionProvider
,以便能够返回[Connection][4]
而不是[ConnectionProvider][5]
public class MultiTenantContextConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private DataSource lazyDatasource;;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();
lazyDatasource = (DataSource) lSettings.get( Environment.DATASOURCE );
}
@Override
public Connection getAnyConnection() throws SQLException {
return lazyDatasource.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'");
}
catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
}
return connection;
}
}
答案 0 :(得分:33)
您可以选择3种不同的策略来影响连接轮询。无论如何,您必须提供MultiTenantConnectionProvider
的实现。您选择的策略当然会影响您的实施。
关于MultiTenantConnectionProvider.getAnyConnection()
getAnyConnection()
来收集元数据并设置SessionFactory。通常在多租户架构中,您有一个未被任何租户使用的特殊/主数据库(或架构)。它是一种模板数据库(或模式)。如果此方法返回与此数据库(或架构)的连接,则可以。
策略1:每个租户都拥有自己的数据库。(以及它自己的连接池)
在这种情况下,每个租户都拥有由C3PO管理的自己的连接池,您可以根据MultiTenantConnectionProvider
提供AbstractMultiTenantConnectionProvider
的实施
每个租户都有自己的C3P0ConnectionProvider
,因此您在selectConnectionProvider(tenantIdentifier)
所要做的就是返回正确的租户。你可以保留一个Map来缓存它们,你可以使用以下内容来懒惰初始化C3POConnectionProvider:
private ConnectionProvider lazyInit(String tenantIdentifier){
C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
connectionProvider.configure(getC3POProperties(tenantIdentifier));
return connectionProvider;
}
private Map getC3POProperties(String tenantIdentifier){
// here you have to get the default hibernate and c3po config properties
// from a file or from Spring application context (there are good chances
// that those default properties point to the special/master database)
// and alter them so that the datasource point to the tenant database
// i.e. : change the property hibernate.connection.url
// (and any other tenant specific property in your architecture like :
// hibernate.connection.username=tenantIdentifier
// hibernate.connection.password=...
// ...)
}
策略2:每个租户拥有自己的架构,并且在单个数据库中拥有自己的连接池
此案例与关于ConnectionProvider
实施的第一个策略非常相似,因为您也可以使用AbstractMultiTenantConnectionProvider
作为基类来实现MultiTenantConnectionProvider
该实现非常类似于策略1的建议实现,除了您必须在c3po配置中更改模式而不是数据库
策略3:每个租户在单个数据库中拥有自己的架构,但使用共享连接池
这种情况略有不同,因为每个租户都将使用相同的连接提供程序(因此将共享连接池)。在这种情况下:连接提供程序必须在使用任何连接之前设置要使用的架构。即您必须实施MultiTenantConnectionProvider.getConnection(String tenantIdentifier)
(即AbstractMultiTenantConnectionProvider
提供的默认实施不起作用。)
使用postgresql,您可以执行以下操作:
SET search_path to <schema_name_for_tenant>;
或使用别名
SET schema <schema_name_for_tenant>;
所以这是getConnection(tenant_identifier);
的样子:
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "SET search_path TO " + tenanantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" +
tenantIdentifier + "]",
e
);
}
return connection;
}
有用的参考是here(官方文件)
其他有用的链接C3POConnectionProvider.java
您可以在实施中结合策略1和策略2。您只需要一种方法来为当前租户找到正确的连接属性/连接URL。
修改强>
我认为策略2或3之间的选择取决于您的应用的流量和租户数量。使用单独的连接池:一个租户可用的连接数量将会低很多,因此:如果出于某种合法原因,一个租户突然需要很多连接,这个特定租户所看到的性能将大幅下降(而另一个租户将不会影响)。
另一方面,对于策略3,如果出于某种合法的原因,一个租户突然需要很多联系:每个租户看到的表现都会减少。
一般来说,我认为策略2更灵活,更安全:每个租户不能消耗超过一定数量的连接(如果需要,可以为每个租户配置此数量)
答案 1 :(得分:0)
为租户选择(1)架构或(2)单独的数据库取决于您可以为租户预期的数据量。但是,以下考虑值得研究
为试用客户和低级客户创建共享架构模型 批量客户,这可以通过数量来识别 您在租赁过程中向租户提供的功能 招待客户
创建或加载可能的企业级客户时 拥有大量的交易数据,非常适合单独使用 数据库中。
架构模型可能具有不同的SQL Server实现 和MySQL服务器不同的一个,你应该考虑。
在选择选项时,请考虑客户[租户]可能愿意在相当长的时间和系统使用后扩展的事实。如果您的应用中不支持适当的横向扩展选项,则必须加以打扰。
分享您对以上几点的评论,进一步讨论