当前位置: 首页 > 知识库问答 >
问题:

使用Spring、Hibernate和C3P0管理多租户web应用程序中的连接池

顾承平
2023-03-14

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

  • Spring 4.0。0
  • 冬眠4.2。8
  • Hibernate-c3p04.2。8(使用c3p0-0.9.2.1)
  • 以及PostgreSQL 9.3(我怀疑它对整个体系结构是否真的重要)

大多数时候,我遵循这个线程(因为@Transactional解决方案)。但是我有点迷失在实现MultiTenantContextConnectionProvider中。在SO上也有类似的问题,但有些方面我不明白:

1)连接池会发生什么?我是说,它是由Spring还是Hibernate管理的?我猜对于ConnectionProviderBuilder-或者按照建议--它的任何实现,Hibernate都是管理它的人。或者有没有可能是Spring管理的?
3)这是未来实现数据库和模式分离的正确路径吗?

如有任何评论或描述,我们将不胜感激。

应用程序上下文。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>

esolver.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;
    }
}

共有2个答案

王德华
2023-03-14

依我看,连接池管理将默认由Sql Server本身处理,但是一些编程语言(如C#)确实提供了一些控制池的方法。参考这里

租户的(1)模式或(2)独立数据库的选择取决于您可以为租户预期的数据量。但是,以下考虑因素值得研究

>

  • 为试用客户和小批量客户创建共享模式模型,这可以通过您在客户入职过程中向租户提供的功能数量来确定

    当您创建或加入可能有大量事务数据的企业级客户时,最好使用单独的数据库。

    模式模型对于SQL服务器可能有不同的实现,对于MySQL服务器可能有不同的实现,您应该考虑这一点。

    此外,当选择该选项时,请考虑客户(租户)可能愿意在相当长的时间和系统使用之后扩大规模这一事实。如果你的应用程序中不支持适当的扩展选项,你将不得不为此烦恼。

    分享您对以上几点的意见,以便进一步展开讨论

  • 郜玉石
    2023-03-14

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

    关于MultiTenantConnectionProvider的一般说明。getAnyConnection()

    hibernate需要getAnyConnection()来收集元数据并设置SessionFactory。通常在多租户体系结构中,您有一个特殊的/主数据库(或模式),任何租户都不使用。它是一种模板数据库(或模式)。如果此方法返回到此数据库(或架构)的连接,则没有问题。

    策略1:每个租户都有自己的数据库。(因此它是自己的连接池)

    在这种情况下,每个租户都有自己的连接池,由C3PO管理,您可以基于abstractMultitenatConnectionProvider

    每个租户都有自己的C3P0ConnectionProvider,因此在selectConnectionProvider(tenantIdentifier)中只需返回正确的一个即可。您可以保留一个映射来缓存它们,还可以使用以下内容初始化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 tenant标识符)(即由AbstractMultiTenantConnectionProvider提供的默认实现将不起作用)。

    与postgreql你可以做到这一点:

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

    这里有有用的参考资料(官方文件)

    其他有用的链接C3POConnectionProvider。JAVA

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

    编辑

    我认为策略2或3之间的选择取决于应用上的流量和租户数量。使用单独的连接池:一个租户可用的连接量将低得多,因此:如果由于某些长期原因,一个租户突然需要许多连接,则该特定租户的性能将大幅下降(而另一个租户不会受到影响)。

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

    一般来说,每个租户的消费量不能超过2,而且每个租户的消费量不能超过2

     类似资料:
    • 问题内容: 我正在尝试建立一个多租户的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 ma

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

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

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

    • 我已经在谷歌上搜索了4个多小时的stackoverflow来寻找我的问题的解决方案,阅读并试图了解正在发生的事情,但我没有遇到与我的问题相关的解决方案,所以抱歉,如果这听起来像是重复的,我会尽力解释到底发生了什么,这样我就可以深入了解C3P0的内部工作。 我有一个运行在Tomcat上的SpringMVC web应用程序,其中Hibernate、JPA和C3P0是我的池资源。重新加载页面时,应用程序

    • 假设我有一个应用程序,具有以下需求。 它有一个类(实体) 每个用户有零个或多个(一对多实体关系) 每个具有零个或多个(一对多实体关系) null 现在有要求... 只能创建/编辑/删除自己的实体 只能在自己的中创建/编辑/删除 实体 另外,假设实体与集成,并且我们知道contoller和服务中的当前用户。 正如您所看到的…随着作用域资源的数量的增加…这个类将会变得非常臃肿并且很难维护。 有没有人知