with udemy tutorial mvc example spring hibernate postgresql multi-tenant c3p0

udemy - spring mvc 5 tutorial



Administre la agrupaciĆ³n de conexiones en la aplicaciĆ³n web multiusuario con Spring, Hibernate y C3P0 (2)

Estoy tratando de configurar una aplicación web para múltiples inquilinos, con (idealmente) la posibilidad de un enfoque separado por la Base de datos y por el Esquema al mismo tiempo. Aunque voy a empezar con la separación de esquemas. Actualmente estamos usando:

  • Primavera 4.0.0
  • Hibernar 4.2.8
  • Hibernate-c3p0 4.2.8 (que utiliza c3p0-0.9.2.1)
  • y PostgreSQL 9.3 (lo cual dudo que realmente importe para la arquitectura general)

Principalmente seguí este hilo (debido a la solución para @Transactional ). Pero estoy un poco perdido en la implementación de MultiTenantContextConnectionProvider . También hay una pregunta similar aquí en SO, pero hay algunos aspectos que no puedo entender:

1) ¿Qué sucede con la agrupación de conexiones? Quiero decir, ¿es administrado por Spring o Hibernate? Supongo que con ConnectionProviderBuilder , o como se sugirió, cualquiera de su implementación, Hibernate es el tipo que lo maneja.
2) ¿Es un buen enfoque que Spring no gestiona la agrupación de conexiones? o ¿Es posible que Spring lo logre?
3) ¿Es este el camino correcto para la futura implementación de la separación entre la Base de datos y el Esquema?

Cualquier comentario o descripción son totalmente apreciados.

aplicación-contexto.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. } }


Editar

Respecto a la respuesta de @ ben75:

Esta es una nueva implementación de MultiTenantContextConnectionProvider . Ya no se extiende AbstractMultiTenantConnectionProvider . Más bien implementa MultiTenantConnectionProvider , para poder devolver [Connection][4] lugar 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; } }


En mi humilde opinión, la administración del conjunto de conexiones se manejará de manera predeterminada por el propio servidor SQL, sin embargo, algunos lenguajes de programación como C # ofrecen algunas formas de controlar los conjuntos. Refiera here

La elección de (1) esquema o (2) base de datos separada para un inquilino depende del volumen de los datos que puede anticipar para el inquilino. Sin embargo, la siguiente consideración puede valer la pena considerar

  1. cree un modelo de esquema compartido para los clientes de prueba y los clientes de bajo volumen, esto se puede identificar por el número de funciones que le proporciona a un inquilino durante el proceso de incorporación de un cliente

  2. cuando creas o incorporas un cliente de nivel empresarial que puede tener una gran cantidad de datos transaccionales, es ideal buscar una base de datos separada.

  3. El modelo de esquema puede tener una implementación diferente para SQL Server y otra diferente para MySQL Server, lo que debe considerar.

  4. también al elegir la opción, tenga en cuenta el hecho de que un cliente [inquilino] puede estar dispuesto a escalar después de una cantidad considerable de tiempo y uso del sistema. Si no hay una opción de ampliación de escala compatible con tu aplicación, deberás ser molestado.

Comparta sus comentarios sobre los puntos anteriores, para profundizar más en esta discusión.


Puede elegir entre 3 estrategias diferentes que afectarán el sondeo de conexión. En cualquier caso, debe proporcionar una implementación de MultiTenantConnectionProvider . La estrategia que elija, por supuesto, afectará su implementación.

Observación general sobre MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() requiere que getAnyConnection() recopile metadatos y configure SessionFactory. Por lo general, en una arquitectura de varios inquilinos, tiene una base de datos maestra / especial (o esquema) que ningún inquilino ha usado. Es un tipo de base de datos de plantillas (o esquema). Está bien si este método devuelve una conexión a esta base de datos (o esquema).

Estrategia 1: cada inquilino tiene su propia base de datos. (y así es su propio grupo de conexión)

En este caso, cada inquilino tiene su propio grupo de conexiones administrado por C3PO y puede proporcionar una implementación de MultiTenantConnectionProvider basada en AbstractMultiTenantConnectionProvider

Cada inquilino tiene su propio C3P0ConnectionProvider , por lo que todo lo que tiene que hacer en selectConnectionProvider(tenantIdentifier) es devolver el correcto. Puede mantener un Mapa para guardarlos en la memoria caché y puede inicializar de forma perezosa un C3POConnectionProvider con algo como:

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

Estrategia 2: cada inquilino tiene su propio esquema y su propio conjunto de conexiones en una sola base de datos

Este caso es muy similar a la primera estrategia con respecto a la implementación de ConnectionProvider , ya que también puede usar AbstractMultiTenantConnectionProvider como clase base para implementar su MultiTenantConnectionProvider

La implementación es muy similar a la implementación sugerida para la Estrategia 1, excepto que debe modificar el esquema en lugar de la base de datos en la configuración de c3po

Estrategia 3: cada inquilino tiene su propio esquema en una sola base de datos pero usa un grupo de conexiones compartidas

Este caso es ligeramente diferente ya que cada inquilino usará el mismo proveedor de conexión (y así se compartirá el grupo de conexiones). En el caso: el proveedor de conexión debe configurar el esquema para que se use antes de cualquier uso de la conexión. es decir, debe implementar MultiTenantConnectionProvider.getConnection(String tenantIdentifier) (es decir, la implementación predeterminada provista por AbstractMultiTenantConnectionProvider no funcionará).

Con postgresql puedes hacerlo con:

SET search_path to <schema_name_for_tenant>;

o usando el alias

SET schema <schema_name_for_tenant>;

Así que aquí está lo que tu getConnection(tenant_identifier); se vera como:

@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 referencia útil está here (documento oficial)

Otro enlace útil C3POConnectionProvider.java

Puedes combinar estrategia 1 y estrategia 2 en tu implementación. Solo necesita una forma de encontrar las propiedades de conexión / url de conexión correctas para el inquilino actual.

EDITAR

Creo que la elección entre la estrategia 2 o 3 depende del tráfico y del número de inquilinos en su aplicación. Con grupos de conexiones separados: la cantidad de conexiones disponibles para un inquilino será mucho menor y, por lo tanto, si por alguna razón legítima un inquilino necesita repentinamente muchas conexiones, el rendimiento observado por este inquilino en particular disminuirá drásticamente (mientras que el otro no será impactado).

Por otro lado, con la estrategia 3, si por alguna razón legítima, un inquilino necesita repentinamente muchas conexiones: el rendimiento observado por cada inquilino disminuirá.

En general, creo que la estrategia 2 es más flexible y segura: cada inquilino no puede consumir más de una cantidad determinada de conexión (y esta cantidad puede configurarse por inquilino si la necesita)