Gérer la mise en commun des connexions dans l'application web multi-locataires avec Spring, Hibernate et C3P0

j'essaie de configurer une application web multi-locataire, avec (idéalement) la possibilité d'une approche séparée de la base de données et du schéma en même temps. Mais je vais commencer par la séparation schématique. Nous utilisons actuellement:

  • printemps 4.0.0
  • hibernation 4.2.8
  • hibernation - c3p0 4.2.8 (qui utilise c3p0-0.9.2.1)
  • et PostgreSQL 9.3 (dont je doute vraiment questions pour l'architecture d'ensemble)

la plupart du temps, j'ai suivi ce fil (à cause de la solution pour @Transactional ). Mais je suis un peu perdu dans l'implémentation de MultiTenantContextConnectionProvider . Il ya aussi cette question similaire demandé ici, mais il ya certains aspects que je ne peux pas comprendre:

1) qu'arrive-t-il à la mise en commun des connexions? C'est géré par le printemps ou L'hibernation? Je suppose qu'avec ConnectionProviderBuilder - ou comme suggéré - de sa mise en œuvre, Hibernate est le gars qui le gère.

2) est-ce une bonne approche que Spring ne gère pas la mise en commun des connexions? ou est-il même possible que le printemps s'en occupe?

3) est-ce la bonne voie pour la mise en œuvre future de la séparation des bases de données et des schémas?

tous les commentaires ou descriptions sont totalement appréciés.

l'application de contexte.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;
    }
}

Multitenantcontextconnection Provider.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.
    }
}




modifier

concernant la réponse de @ben75:

il s'agit d'une nouvelle implémentation de MultiTenantContextConnectionProvider . Il ne s'étend plus AbstractMultiTenantConnectionProvider . Il met plutôt en œuvre MultiTenantConnectionProvider , pour pouvoir retourner [Connection][4] au lieu de [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;
    }
}
26
demandé sur Community 2014-01-20 03:09:15

2 réponses

vous pouvez choisir entre 3 stratégies différentes qui auront un impact sur le sondage de connexion. Dans tous les cas, vous devez fournir une mise en œuvre de MultiTenantConnectionProvider . La stratégie que vous choisissez aura bien sûr un impact sur votre mise en œuvre.

remarque générale sur MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() est requis par hibernate pour collecter les métadonnées et configurer le logiciel SessionFactory. Habituellement, dans une architecture multi-locataires, vous disposez d'une base de données (ou schéma) Spéciale/Principale qui n'est utilisée par aucun locataire. C'est une sorte de base de données de modèle (ou schéma). C'est ok si cette méthode renvoie une connexion à cette base de données (ou schéma).

stratégie 1: chaque locataire possède sa propre base de données. (et donc sa propre piscine de connexion)

dans ce cas, chaque locataire a sa propre piscine de connexion gérée par C3PO et vous pouvez fournir un mise en œuvre de MultiTenantConnectionProvider basée sur AbstractMultiTenantConnectionProvider

chaque locataire a son propre C3P0ConnectionProvider , donc tout ce que vous avez à faire dans selectConnectionProvider(tenantIdentifier) est de retourner le bon. Vous pouvez garder une carte pour les mettre en cache et vous pouvez paresseux-initialiser un C3POConnectionProvider avec quelque chose comme:

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=...
    //     ...) 
}

stratégie 2 : chaque locataire avoir son propre schéma et son propre pool de connexion dans une seule base de données

ce cas est très similaire à la première stratégie concernant la mise en œuvre de ConnectionProvider car vous pouvez également utiliser AbstractMultiTenantConnectionProvider comme classe de base pour mettre en œuvre votre MultiTenantConnectionProvider

la mise en œuvre est très similaire à la mise en œuvre suggérée pour la stratégie 1 sauf que vous devez modifier le schéma à la place de la base de données dans la configuration c3po

stratégie 3: chaque locataire a son propre schéma dans une seule base de données, mais utiliser un pool de connexion partagée

ce cas est légèrement différent puisque chaque locataire utilisera le même fournisseur de connexion (et donc le pool de connexion sera partagé). Dans le cas : le fournisseur de connexion doit définir le schéma d'utilisation avant toute utilisation de la connexion. i.e. vous devez mettre en œuvre MultiTenantConnectionProvider.getConnection(String tenantIdentifier) (c'est à dire l'implémentation par défaut fourni par AbstractMultiTenantConnectionProvider ne fonctionne pas).

avec postgresql vous pouvez le faire avec:

 SET search_path to <schema_name_for_tenant>;

ou en utilisant l'alias

 SET schema <schema_name_for_tenant>;

alors voici à quoi ressemblera votre 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;
}

la référence utile est ici (doc officiel)

autre lien utile C3poconnection Provider.java


vous pouvez combiner stratégie 1 et stratégie 2 dans votre mise en œuvre. Vous avez juste besoin d'un moyen de trouver les bonnes propriétés de connexion/url de connexion pour le locataire actuel.


modifier

je pense que le choix entre la stratégie 2 ou 3 dépend du trafic et le nombre de locataires votre application. Avec des pools de connexion séparés: le nombre de connexions disponibles pour un locataire sera beaucoup plus faible et donc: si pour une raison légitime un locataire a besoin soudainement de nombreuses connexions la performance vue par ce locataire particulier va radicalement diminuer (tandis que l'autre locataire ne sera pas affecté).

d'autre part, avec la stratégie 3, Si pour quelque raison légitime un locataire a soudainement besoin de beaucoup de connexions: la performance vue par chaque locataire diminuera.

en général, je pense que la stratégie 2 est plus flexible et sûre : chaque locataire ne peut pas consommer plus qu'une certaine quantité de connexion (et cette quantité peut être configurée par locataire si vous en avez besoin)

32
répondu ben75 2017-12-07 21:27:11

IMHO, la gestion de la piscine de connexion sera gérée par défaut par le serveur Sql lui-même, cependant certains langages de programmation comme C# offrent des moyens de contrôler les piscines. Référence ici

le choix du (1) schéma ou (2) base de données séparée pour un locataire dépend du volume des données que vous pouvez anticiper pour le locataire. Toutefois, la considération suivante peut être utile d'examiner dans

  1. créer un modèle de schéma partagé pour les clients d'essai et le bas le volume de clients, cela peut être identifié par le numéro de la les fonctionnalités que vous fournissez à un locataire pendant le processus de accueil d'un client

  2. lorsque vous créez ou embarquez un client au niveau de l'entreprise qui peut ont une grande données transactionnelles, il est idéal pour aller pour une autre la base de données.

  3. Le modèle de schéma peut avoir un mise en œuvre différente pour le serveur SQL et un autre pour le serveur MySQL, que vous devriez considérer.

  4. aussi, lorsque vous choisissez l'option, tenez compte du fait qu'un client [locataire] peut être disposé à prendre de l'expansion après une période considérable de temps et d'utilisation du système. S'il n'y a pas d'option d'élimination appropriée prise en charge dans votre application, vous devrez être gêné.

Partagez vos commentaires sur les points ci-dessus, à prendre cette discussion plus loin

0
répondu Saravanan 2014-09-10 12:31:42