spring - 使用 Spring、Hibernate 和 C3P0 管理 Multi-Tenancy Web 应用程序中的连接池

标签 spring hibernate postgresql multi-tenant c3p0

我正在尝试设置一个 Multi-Tenancy Web 应用程序,(理想情况下)可以同时使用数据库分离和模式分离的方法。虽然我将从模式分离开始。我们目前正在使用:

  • Spring 4.0.0
  • hibernate 4.2.8
  • Hibernate-c3p0 4.2.8(使用 c3p0-0.9.2.1)
  • 和 PostgreSQL 9.3(我怀疑它对整体架构真的很重要)

我主要关注 this thread(因为 @Transactional 的解决方案)。但是我在实现 MultiTenantContextConnectionProvider 时有点迷失了。 SO上这里也有this similar question问,但是有些方面我想不通:

1) 连接池会发生什么?我的意思是,它是由 Spring 还是 Hibernate 管理的?我猜想对于 ConnectionProviderBuilder - 或者按照建议 - 它的任何实现,Hibernate 都是管理它的人。
2)Spring不管理连接池是一种好方法吗?或者 Spring 是否有可能管理它?
3)这是 future 实现数据库和模式分离的正确途径吗?

非常感谢任何评论或描述。

application-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;
    }
}

最佳答案

您可以在 3 种会影响连接轮询的不同策略之间进行选择。在任何情况下,您都必须提供 MultiTenantConnectionProvider 的实现。 .您选择的策略当然会影响您的实现。

关于MultiTenantConnectionProvider.getAnyConnection()

的一般说明

getAnyConnection() hibernate 需要收集元数据并设置 SessionFactory。通常在 Multi-Tenancy 架构中,您有一个未被任何租户使用的特殊/主数据库(或模式)。它是一种模板数据库(或模式)。如果这个方法返回一个到这个数据库(或模式)的连接就可以了。

策略 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 更加灵活和安全:每个租户不能消耗超过给定数量的连接(如果需要,可以为每个租户配置此数量)

关于spring - 使用 Spring、Hibernate 和 C3P0 管理 Multi-Tenancy Web 应用程序中的连接池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21223894/

相关文章:

java - 基于 Spring-ws @Endpoint/@PayloadRoot 的配置给出 404

没有STOMP,sockjs的Spring 4 websocket

java - 如何用真实数据库表填充 h2 内存表?

java - 在多对多 hibernate 中覆盖关系表数据

postgresql - 如何使用liquibase中的一些默认时间戳更新日期列

mysql - 数据库模型和迁移说明

java - Spring MVC 新手

java - Hibernate - 使用mappedBy时外键为空

java - 隐藏删除的对象

java - JPA hibernate : Invalid persistence. xml