Управление пулом соединений в многопользовательском веб-приложении с помощью Spring, Hibernate и C3P0

Я пытаюсь настроить многопользовательское веб-приложение, причем (в идеале) возможность как для разделения базы данных, так и для разделения схем. Хотя я собираюсь начать с разделения Схемы. В настоящее время мы используем:

  • Spring 4.0.0
  • Hibernate 4.2.8
  • Hibernate-c3p0 4.2.8 (который использует c3p0-0.9.2.1)
  • и PostgreSQL 9.3 (что я сомневаюсь, что это действительно важно для общей архитектуры)

В основном я следил за этот поток (из-за решения для @Transactional). Но я немного потерял в реализации MultiTenantContextConnectionProvider. Существует также этот подобный вопрос, заданный здесь на 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:

Это новая реализация 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;
    }
}

Ответ 1

Вы можете выбрать между тремя различными стратегиями, которые влияют на опрос соединений. В любом случае вам необходимо обеспечить реализацию MultiTenantConnectionProvider. Стратегия, которую вы выберете, будет, конечно, влиять на вашу реализацию.

Общее замечание о MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() требуется спящий режим для сбора метаданных и настройки SessionFactory. Обычно в архитектуре с несколькими арендаторами у вас есть специальная/основная база данных (или схема), не используемая ни одним арендатором. Это своего рода база данных шаблонов (или схема). Это нормально, если этот метод возвращает соединение с этой базой данных (или схемой).

Стратегия 1: у каждого арендатора есть собственная база данных. (и, следовательно, собственный пул подключений)

В этом случае у каждого арендатора есть собственный пул подключений, управляемый C3PO, и вы можете обеспечить реализацию MultiTenantConnectionProvider на основе AbstractMultiTenantConnectionProvider

У каждого арендатора есть свой 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 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;
}

Полезная ссылка здесь (официальный документ)

Другая полезная ссылка C3POConnectionProvider.java


Вы можете комбинировать стратегию 1 и стратегию 2 в своей реализации. Вам просто нужен способ найти правильные свойства соединения /url подключения для текущего арендатора.


ИЗМЕНИТЬ

Я думаю, что выбор между стратегией 2 или 3 зависит от трафика и количества арендаторов в вашем приложении. С отдельными пулами подключений: количество подключений, доступных для одного арендатора, будет намного ниже, и поэтому: если для какой-то легитимной причины одному арендатору вдруг потребуется много соединений, производительность, видимая этим конкретным арендатором, будет резко уменьшаться (в то время как другой арендатор не будет воздействие).

С другой стороны, со стратегией 3, если для какой-то легитимной причины одному арендатору вдруг нужно много соединений: производительность, наблюдаемая каждым арендатором, будет уменьшаться.

В целом, я думаю, что стратегия 2 более гибкая и безопасная: каждый арендатор не может потреблять больше определенного количества соединения (и эта сумма может быть настроена для каждого арендатора, если вам это нужно)

Ответ 2

IMHO, управление пулом соединений будет выполняться по умолчанию самим сервером Sql, однако некоторые языки программирования, такие как С#, предлагают некоторые способы управления пулами. Обратитесь здесь

Выбор (1) схемы или (2) отдельной базы данных для арендатора зависит от объема данных, которые вы можете ожидать для арендатора. Тем не менее, следующее соображение может стоить взглянуть на

  • создать общую модель схемы для пробных клиентов и объемных клиентов, это можно определить по количеству функции, которые вы предоставляете арендатору в процессе на борту клиента

  • при создании или на уровне клиента уровня предприятия, который может имеют большие транзакционные данные, идеально подходит для отдельного базы данных.

  • Модель схемы может иметь другую реализацию для SQL Server и другой для MySQL Server, который вы должны рассмотреть.

  • также при выборе опции учитывайте тот факт, что клиент [арендатор] может захотеть масштабироваться после значительного количества времени и использования системы. Если в приложении нет соответствующей опции масштабирования, вам придется беспокоиться.

Поделитесь своими комментариями по вышеуказанным пунктам, чтобы продолжить обсуждение