当前位置: 首页 > 面试题库 >

使用Spring,Hibernate和C3P0在多租户Web应用程序中管理连接池

姚乐家
2023-03-14
问题内容

我正在尝试建立一个多租户的Web应用程序,同时(理想情况下)可以同时使用数据库分隔和模式分隔的方法。尽管我将从模式分离开始。我们目前正在使用:

  • Spring 4.0.0
  • Hibernate 4.2.8
  • Hibernate-c3p0 4.2.8 (which uses c3p0-0.9.2.1)
  • and PostgreSQL 9.3 (which I doubt it really matters for the overall architecture)

通常,我遵循该线程(因为针对的解决方案@Transactional)。但是我有点迷失于实施MultiTenantContextConnectionProvider。在SO上也有类似的问题,但有些方面我不知道:

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

任何评论或描述都将不胜感激。

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 的答案:

这是的新实现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;
    }
}

问题答案:

你可以选择三种会影响连接轮询的不同策略。无论如何,你都必须提供的实现MultiTenantConnectionProvider。你选择的策略当然会影响你的实施。

关于的一般性评论 MultiTenantConnectionProvider.getAnyConnection()

hibernate需要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;
}

你可以在实现中结合策略1和策略2。你只需要一种方法来找到当前租户的正确连接属性/连接URL。

编辑

我认为策略2或策略3的选择取决于流量和应用程序上的租户数量。使用单独的连接池:一个租户可用的连接数量将大大减少,因此:如果某个租户出于某种合法原因突然需要许多连接,则该特定租户看到的性能将急剧下降(而另一个租户不会受影响)。

另一方面,在策略3中,如果某个租户出于某种合法原因突然需要许多联系:每个租户看到的性能都会下降。

总的来说,我认为策略2更灵活,更安全:每个租户都不能消耗超过给定数量的连接(如果需要,可以为每个租户配置此数量)



 类似资料:
  • 我正在尝试设置一个多租户web应用程序,在理想情况下,可以同时使用数据库分离和模式分离的方法。虽然我将从模式分离开始。我们目前正在使用: Spring 4.0。0 冬眠4.2。8 Hibernate-c3p04.2。8(使用c3p0-0.9.2.1) 以及PostgreSQL 9.3(我怀疑它对整个体系结构是否真的重要) 大多数时候,我遵循这个线程(因为的解决方案)。但是我有点迷失在实现中。在SO

  • 当hibernate调用getConnection(String tenantIdentifier)时,我想在MYSQL数据库中设置正确的模式。我实现使用了Mysql命令来更改scheme“use sample_tenant_identifier”。我必须使用名为“用户”和“管理员”的用户。每个用户都有自己的模式。我的问题很奇怪。示例所有选择操作使用“user”模式,但插入或更新使用“admin”

  • 我正在使用spring boot开发一个多租户应用程序。 系统的业务逻辑将根据每个租户进行更改。 例如,应用程序上的特定租户租赁空间可能希望改变使用一些复杂的自定义逻辑计算值的方式。我想为应用程序注册默认行为/依赖项,并允许特定租户覆盖它。 这可以使用Spring Boot完成吗?

  • 我正在使用带有MongoDB的Node-MongoDB本机驱动程序来编写一个网站。 我有一些关于如何管理连接的问题: > 对所有请求只使用一个MongoDB连接是否足够?是否存在性能问题?如果不是,我可以设置一个全局连接在整个应用程序中使用吗? 如果不是,如果我在请求到达时打开一个新的连接,并且在处理请求时关闭它,这是好的吗?打开和关闭一个连接是否昂贵? 我应该使用全局连接池吗?我听说驱动程序有一

  • 问题内容: 我在MongoDB中使用node-mongodb-native驱动程序来编写网站。 我对如何管理连接有一些疑问: 仅使用一个MongoDB连接就可以处理所有请求是否足够?是否存在性能问题?如果没有,是否可以设置全局连接以在整个应用程序中使用? 如果没有,如果在请求到达时打开一个新连接,并在处理请求时将其关闭,那会很好吗?打开和关闭连接是否昂贵? 我应该使用全局连接池吗?我听说驱动程序具

  • 我正在使用Spring 2. x、Hibernate 5. x、Spring Data REST、Mysql 5.7构建一个多租户REST服务器应用程序。Spring 2. x使用Hikari进行连池。 我将使用每个租户数据库的方法,这样每个租户都有自己的数据库。 我以这种方式创建了我的MultiTenantConnectionProvider: 在我的实现中,我阅读了tenantId,并从中央管