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
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
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.
El modelo de esquema puede tener una implementación diferente para SQL Server y otra diferente para MySQL Server, lo que debe considerar.
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)