tenancy multi spring hibernate orm multi-tenant

tenancy - Configuración de un proveedor de MultiTenantConnectionProvider con Hibernate 4.2 y Spring 3.1.1



java spring multi tenancy (5)

A partir de la versión 3.2.4 de Spring Framework, no hay forma de que MultiTenantConnectionProvider y CurrentTenantIdentifierResolver sean administrados por el contenedor Spring. Esto crea muchos obstáculos, como el uso de DataSource, WebContext y otros beans y características administrados por Spring. He intentado encontrar soluciones más limpias, pero se me ocurrió una sola:

Extienda org.springframework.orm.hibernate4.LocalSessionFactoryBuilder y escriba un LocalSessionFactoryBean personalizado (no se puede crear una subclase y proporcionar un LocalSessionFactoryBuilder, es básicamente una copia del original con un pequeño cambio)

Aquí va:

package com.levitech.hibernate; import javax.sql.DataSource; import org.hibernate.cfg.Environment; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.core.io.ResourceLoader; public class CustomLocalSessionFactoryBuilder extends org.springframework.orm.hibernate4.LocalSessionFactoryBuilder { public CustomLocalSessionFactoryBuilder(DataSource dataSource,ResourceLoader resourceLoader, MultiTenantConnectionProvider connectionProvider, CurrentTenantIdentifierResolver tenantIdResolver) { super(dataSource, resourceLoader); getProperties().put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider); getProperties().put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdResolver); } }

El reemplazo LocalSessionFactoryBean (el único cambio es en el método afterPropertiesSet () para usar el LocalSessionFactoryBuilder personalizado):

package com.levitech.hibernate; /* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.IOException; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.Interceptor; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.NamingStrategy; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.orm.hibernate4.HibernateExceptionTranslator; import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder; /** * {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate * {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared * Hibernate SessionFactory in a Spring application context; the SessionFactory can * then be passed to Hibernate-based data access objects via dependency injection. * * <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher. * It is similar in role to the same-named class in the {@code orm.hibernate3} package. * However, in practice, it is closer to {@code AnnotationSessionFactoryBean} since * its core purpose is to bootstrap a {@code SessionFactory} from annotation scanning. * * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make * sure to either specify the {@link #setJtaTransactionManager "jtaTransactionManager"} * bean property or to set the "hibernate.transaction.factory_class" property to * {@link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}. * Otherwise, Hibernate''s smart flushing mechanism won''t work properly. * * @author Juergen Hoeller * @since 3.1 * @see #setDataSource * @see #setPackagesToScan * @see LocalSessionFactoryBuilder */ public class CustomLocalSessionFactoryBean extends HibernateExceptionTranslator implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean { private MultiTenantConnectionProvider multiTenantConnectionProvider; private CurrentTenantIdentifierResolver tenantIdResolver; private DataSource dataSource; private Resource[] configLocations; private String[] mappingResources; private Resource[] mappingLocations; private Resource[] cacheableMappingLocations; private Resource[] mappingJarLocations; private Resource[] mappingDirectoryLocations; private Interceptor entityInterceptor; private NamingStrategy namingStrategy; private Properties hibernateProperties; private Class<?>[] annotatedClasses; private String[] annotatedPackages; private String[] packagesToScan; private Object jtaTransactionManager; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private Configuration configuration; private SessionFactory sessionFactory; public MultiTenantConnectionProvider getMultiTenantConnectionProvider() { return multiTenantConnectionProvider; } public void setMultiTenantConnectionProvider( MultiTenantConnectionProvider multiTenantConnectionProvider) { this.multiTenantConnectionProvider = multiTenantConnectionProvider; } public CurrentTenantIdentifierResolver getTenantIdResolver() { return tenantIdResolver; } public void setTenantIdResolver(CurrentTenantIdentifierResolver tenantIdResolver) { this.tenantIdResolver = tenantIdResolver; } /** * Set the DataSource to be used by the SessionFactory. * If set, this will override corresponding settings in Hibernate properties. * <p>If this is set, the Hibernate settings should not define * a connection provider to avoid meaningless double configuration. */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Set the location of a single Hibernate XML config file, for example as * classpath resource "classpath:hibernate.cfg.xml". * <p>Note: Can be omitted when all necessary properties and mapping * resources are specified locally via this bean. * @see org.hibernate.cfg.Configuration#configure(java.net.URL) */ public void setConfigLocation(Resource configLocation) { this.configLocations = new Resource[] {configLocation}; } /** * Set the locations of multiple Hibernate XML config files, for example as * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml". * <p>Note: Can be omitted when all necessary properties and mapping * resources are specified locally via this bean. * @see org.hibernate.cfg.Configuration#configure(java.net.URL) */ public void setConfigLocations(Resource[] configLocations) { this.configLocations = configLocations; } /** * Set Hibernate mapping resources to be found in the class path, * like "example.hbm.xml" or "mypackage/example.hbm.xml". * Analogous to mapping entries in a Hibernate XML config file. * Alternative to the more generic setMappingLocations method. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see #setMappingLocations * @see org.hibernate.cfg.Configuration#addResource */ public void setMappingResources(String[] mappingResources) { this.mappingResources = mappingResources; } /** * Set locations of Hibernate mapping files, for example as classpath * resource "classpath:example.hbm.xml". Supports any resource location * via Spring''s resource abstraction, for example relative paths like * "WEB-INF/mappings/example.hbm.xml" when running in an application context. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addInputStream */ public void setMappingLocations(Resource[] mappingLocations) { this.mappingLocations = mappingLocations; } /** * Set locations of cacheable Hibernate mapping files, for example as web app * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location * via Spring''s resource abstraction, as long as the resource can be resolved * in the file system. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File) */ public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) { this.cacheableMappingLocations = cacheableMappingLocations; } /** * Set locations of jar files that contain Hibernate mapping resources, * like "WEB-INF/lib/example.hbm.jar". * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addJar(java.io.File) */ public void setMappingJarLocations(Resource[] mappingJarLocations) { this.mappingJarLocations = mappingJarLocations; } /** * Set locations of directories that contain Hibernate mapping resources, * like "WEB-INF/mappings". * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File) */ public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) { this.mappingDirectoryLocations = mappingDirectoryLocations; } /** * Set a Hibernate entity interceptor that allows to inspect and change * property values before writing to and reading from the database. * Will get applied to any new Session created by this factory. * @see org.hibernate.cfg.Configuration#setInterceptor */ public void setEntityInterceptor(Interceptor entityInterceptor) { this.entityInterceptor = entityInterceptor; } /** * Set a Hibernate NamingStrategy for the SessionFactory, determining the * physical column and table names given the info in the mapping document. * @see org.hibernate.cfg.Configuration#setNamingStrategy */ public void setNamingStrategy(NamingStrategy namingStrategy) { this.namingStrategy = namingStrategy; } /** * Set Hibernate properties, such as "hibernate.dialect". * <p>Note: Do not specify a transaction provider here when using * Spring-driven transactions. It is also advisable to omit connection * provider settings and use a Spring-set DataSource instead. * @see #setDataSource */ public void setHibernateProperties(Properties hibernateProperties) { this.hibernateProperties = hibernateProperties; } /** * Return the Hibernate properties, if any. Mainly available for * configuration through property paths that specify individual keys. */ public Properties getHibernateProperties() { if (this.hibernateProperties == null) { this.hibernateProperties = new Properties(); } return this.hibernateProperties; } /** * Specify annotated entity classes to register with this Hibernate SessionFactory. * @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class) */ public void setAnnotatedClasses(Class<?>[] annotatedClasses) { this.annotatedClasses = annotatedClasses; } /** * Specify the names of annotated packages, for which package-level * annotation metadata will be read. * @see org.hibernate.cfg.Configuration#addPackage(String) */ public void setAnnotatedPackages(String[] annotatedPackages) { this.annotatedPackages = annotatedPackages; } /** * Specify packages to search for autodetection of your entity classes in the * classpath. This is analogous to Spring''s component-scan feature * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). */ public void setPackagesToScan(String... packagesToScan) { this.packagesToScan = packagesToScan; } /** * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager} * or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate, * if any. * @see LocalSessionFactoryBuilder#setJtaTransactionManager */ public void setJtaTransactionManager(Object jtaTransactionManager) { this.jtaTransactionManager = jtaTransactionManager; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); } public void afterPropertiesSet() throws IOException { LocalSessionFactoryBuilder sfb = new CustomLocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver, multiTenantConnectionProvider, tenantIdResolver); if (this.configLocations != null) { for (Resource resource : this.configLocations) { // Load Hibernate configuration from given location. sfb.configure(resource.getURL()); } } if (this.mappingResources != null) { // Register given Hibernate mapping definitions, contained in resource files. for (String mapping : this.mappingResources) { Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader()); sfb.addInputStream(mr.getInputStream()); } } if (this.mappingLocations != null) { // Register given Hibernate mapping definitions, contained in resource files. for (Resource resource : this.mappingLocations) { sfb.addInputStream(resource.getInputStream()); } } if (this.cacheableMappingLocations != null) { // Register given cacheable Hibernate mapping definitions, read from the file system. for (Resource resource : this.cacheableMappingLocations) { sfb.addCacheableFile(resource.getFile()); } } if (this.mappingJarLocations != null) { // Register given Hibernate mapping definitions, contained in jar files. for (Resource resource : this.mappingJarLocations) { sfb.addJar(resource.getFile()); } } if (this.mappingDirectoryLocations != null) { // Register all Hibernate mapping definitions in the given directories. for (Resource resource : this.mappingDirectoryLocations) { File file = resource.getFile(); if (!file.isDirectory()) { throw new IllegalArgumentException( "Mapping directory location [" + resource + "] does not denote a directory"); } sfb.addDirectory(file); } } if (this.entityInterceptor != null) { sfb.setInterceptor(this.entityInterceptor); } if (this.namingStrategy != null) { sfb.setNamingStrategy(this.namingStrategy); } if (this.hibernateProperties != null) { sfb.addProperties(this.hibernateProperties); } if (this.annotatedClasses != null) { sfb.addAnnotatedClasses(this.annotatedClasses); } if (this.annotatedPackages != null) { sfb.addPackages(this.annotatedPackages); } if (this.packagesToScan != null) { sfb.scanPackages(this.packagesToScan); } if (this.jtaTransactionManager != null) { sfb.setJtaTransactionManager(this.jtaTransactionManager); } // Build SessionFactory instance. this.configuration = sfb; this.sessionFactory = buildSessionFactory(sfb); } /** * Subclasses can override this method to perform custom initialization * of the SessionFactory instance, creating it via the given Configuration * object that got prepared by this LocalSessionFactoryBean. * <p>The default implementation invokes LocalSessionFactoryBuilder''s buildSessionFactory. * A custom implementation could prepare the instance in a specific way (e.g. applying * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass. * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean * @return the SessionFactory instance * @see LocalSessionFactoryBuilder#buildSessionFactory */ protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) { return sfb.buildSessionFactory(); } /** * Return the Hibernate Configuration object used to build the SessionFactory. * Allows for access to configuration metadata stored there (rarely needed). * @throws IllegalStateException if the Configuration object has not been initialized yet */ public final Configuration getConfiguration() { if (this.configuration == null) { throw new IllegalStateException("Configuration not initialized yet"); } return this.configuration; } public SessionFactory getObject() { return this.sessionFactory; } public Class<?> getObjectType() { return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class); } public boolean isSingleton() { return true; } public void destroy() { this.sessionFactory.close(); } }

En su contexto de aplicación defina los beans:

<bean id="multiTenantProvider" class="com.levitech.hibernate.MultiTenantConnectionProviderImpl" depends-on="myDataSource" lazy-init="false"></bean> <bean id="tenantIdResolver" class="com.levitech.hibernate.TenantIdResolver"></bean> <bean id="sessionFactory" class="com.levitech.hibernate.CustomLocalSessionFactoryBean" depends-on="liquibase, myDataSource, multiTenantProvider"> <property name="dataSource" ref="myDataSource"></property> <property name="multiTenantConnectionProvider" ref="multiTenantProvider"></property> <property name="tenantIdResolver" ref="tenantIdResolver"></property> <property name="mappingLocations" value="classpath*:hibernate/**/*.hbm.xml" /> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQL5Dialect hibernate.show_sql=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=true hibernate.multiTenancy=SCHEMA </value> </property> </bean>

NO proporcione valores para lo siguiente en sus propiedades de hibernación: hibernate.tenant_identifier_resolver y hibernate.multi_tenant_connection_provider

Estás listo y todos tus frijoles son manejados por Spring. ¡Eres libre de usar DI otra vez! Espero que esto ayude a alguien. Puse en una solicitud de Jira para la característica ..

Actualmente estoy intentando configurar Hibernate para tenencia múltiple utilizando el enfoque de esquema separado.
Después de trabajar en él durante unos 2 días y navegar por casi todas las fuentes que pude encontrar a través de Google, estoy empezando a frustrarme.

Básicamente estoy tratando de seguir la guía provista en la guía de Hibernate http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691
Pero desafortunadamente no puedo encontrar ConnectionProviderUtils para construir ConnectionProvider. Actualmente estoy tratando de averiguar 2 puntos:

  1. Por qué nunca se llama al método de configuración (propiedades de propiedades) de mi MSSQLMultiTenantConnectionProvider. Por lo que he interpretado de la fuente y la descripción de otras implementaciones de ConnectionProvider diferentes, asumo que este método se llamará para inicializar ConnectionProvider.

  2. Como no puedo trabajar con la configuración (propiedades de propiedades), probé otros métodos para obtener las propiedades de hibernación y DataSource especificadas en el contexto de la aplicación y el hibernate.cfg.xml. (Como inyectar la fuente de datos directamente en ConnectionProvider)

Cualquier puntero a posibles formas de resolver esto (métodos, clases, tutoriales)

Así que aquí están las partes relevantes de mi implementación:
Fuente de datos e Hibernate.cfg.xml:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://<host>:<port>;databaseName=<DbName>;" /> <property name="username" value=<username> /> <property name="password" value=<password> /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <!-- property name="dataSource" ref="dataSource" /--> <property name="annotatedClasses"> <list> <value>c.h.utils.hibernate.User</value> <value>c.h.utils.hibernate.Role</value> <value>c.h.utils.hibernate.Tenant</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.SQLServerDialect hibernate.show_sql=true hibernate.multiTenancy=SCHEMA hibernate.tenant_identifier_resolver=c.h.utils.hibernate.CurrentTenantIdentifierResolver hibernate.multi_tenant_connection_provider=c.h.utils.hibernate.MSSQLMultiTenantConnectionProviderImpl </value> </property> </bean>

MSSQLMultiTenantConnectionProviderImpl:

package c.hoell.utils.hibernate; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class MSSQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider { private static final long serialVersionUID = 8074002161278796379L; @Autowired private DataSource dataSource; public void configure(Properties props) throws HibernateException { } @Override public Connection getAnyConnection() throws SQLException { Properties properties = getConnectionProperties(); //method which sets the hibernate properties DriverManagerConnectionProviderImpl defaultProvider = new DriverManagerConnectionProviderImpl(); defaultProvider.configure(properties); Connection con = defaultProvider.getConnection(); ResultSet rs = con.createStatement().executeQuery("SELECT * FROM [schema].table"); rs.close(); //the statement and sql is just to test the connection return defaultProvider.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { <--not sure how to implement this--> } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection){ try { this.releaseAnyConnection(connection); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public boolean supportsAggressiveRelease() { return false; } @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MSSQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType ); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if ( isUnwrappableAs( unwrapType ) ) { return (T) this; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }

En este momento hay 2 enfoques posibles que veo para obtener las configuraciones que necesito de los archivos de configuración. Obtenga el método de configuración () para ejecutar o de alguna manera haga posible la inyección de DataSource. Supongo que la primera sería la mejor manera.

Una cosa importante a mencionar es que tenía Hibernate en funcionamiento para un solo inquilino (significa sin usar MultiTenantConnectionProvider, usando el ConnectionProvider estándar usado por Hibernate)

Ya muchas gracias a cualquiera que esté leyendo este post. Esperando las respuestas.

Atentamente

Actualización 1:

He jugado un poco con esto un poco y he codificado en detalle las conexiones en mi MultiTenantConnectionProvider (actualizado el Código anterior). Esto está funcionando bien en lo que respecta al MultiTenantConnectionProvider. Pero esto todavía no está resolviendo mis problemas. Ahora mi aplicación falla al inicializar el Administrador de transacciones :

<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>

Esta es la parte superior de la excepción stacktrace :

Causado por: java.lang.NullPointerException en org.springframework.orm.hibernate4.SessionFactoryUtils.getDataSource (SessionFactoryUtils.java:101) en org.springframework.permas de las partes de la que se encuentra en la empresa. .beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods (AbstractAutowireCapableBeanFactory.java:1514) en org.es.as.as.as.as.as.as.as.as.as.as.as.as.as.as.casas de la naturaleza y las

Rastreé este problema en el modo de depuración y descubrí que el problema es que mi SessionFactory de alguna manera no se está apoderando del DataSource. (No importa si especifico el DataSource en el hibernate.cfg.xml o no) Pero al inicializar el TransactionManager, intenta obtener el DataSource del SessionFactory y falla con una excepción NullPointerException como resultado. ¿Alguien tiene una pista en qué punto del funcionamiento interno de hibernación está fallando? En toda la documentación y publicaciones que he visto, no había ninguna indicación de que tuviera que manejar la inyección de DataSource en SessionFactory. Por ahora, solo creo que trato de averiguar cómo colocar un DataSource en el lugar necesario o cómo cambiar el flujo de inicialización. Si alguien tiene una idea mejor, sería muy feliz.

Edit: También publicó esto en los foros de Hibernate ahora:

Actualización 2:

Así que logré solucionar este problema configurando la propiedad autodetectDataSource en TransactionManager en false:

<property name="autodetectDataSource" value="false"/>

Recibí esta sugerencia de la siguiente publicación http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-multi-tenancy-but-no-tenant-identifier-specified . Lamentablemente ahora estoy atascado en exactamente ese problema. ^^ "Pero este es un problema para otro tema. (Edición: Resulta que esto solo fue una mala configuración de las pruebas anteriores + una antigua dependencia)

En cuanto a este tema, el problema sigue siendo que de alguna manera quiero poder reutilizar el DataSource, que ya tengo en la configuración para el uso de Spring Security, para que Hibernate evite la necesidad de tener que configurar el DataSource en dos lugares. Así que la pregunta sigue siendo cómo integrar el uso de DataSource en mi MultiTenantConnectionProvider. ¿Alguien tiene una idea sobre dónde encontrar algún indicio sobre eso?


De acuerdo con los comentarios de Steve Ebersole sobre el problema de JIRA remitido por uno de los comentaristas de esta pregunta ( HHH-8752 ):

Bueno, primero, simplemente no es cierto que Hibernate "ejemplifica las clases referidas por ... MULTI_TENANT_CONNECTION_PROVIDER y MULTI_TENANT_IDENTIFIER_RESOLVER". Hibernate primero trata de tratar estas configuraciones como objetos de sus tipos previstos, (MultiTenantConnectionProvider for MULTI_TENANT_CONNECTION_PROVIDER y CurrentTenantIdentifierResolver for MULTI_TENANT_IDENTIFIER_RESOLVER.

Así que simplemente pase sus beans directamente, configurados como desee.

Simplemente seguí su sugerencia y logré que funcionara.

Este es un CurrentTenantIdentifierResolver definido como Spring Bean:

@Component @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class RequestURITenantIdentifierResolver implements CurrentTenantIdentifierResolver { @Autowired private HttpServletRequest request; @Override public String resolveCurrentTenantIdentifier() { String[] pathElements = request.getRequestURI().split("/"); String tenant = pathElements[1]; return tenant; } @Override public boolean validateExistingCurrentSessions() { return true; } }

Este es un MultiTenantConnectionProvider definido como Spring Bean:

@Component public class SchemaPerTenantConnectionProviderImpl implements MultiTenantConnectionProvider { @Autowired private DataSource dataSource; @Override public Connection getAnyConnection() throws SQLException { return dataSource.getConnection(); } @Override public void releaseAnyConnection(final Connection connection) throws SQLException { connection.close(); } @Override public Connection getConnection(final String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { connection.createStatement().execute("USE " + tenantIdentifier); } catch (SQLException e) { throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseConnection(final String tenantIdentifier, final Connection connection) throws SQLException { try { connection.createStatement().execute("USE dummy"); } catch (SQLException e) { // on error, throw an exception to make sure the connection is not returned to the pool. // your requirements may differ throw new HibernateException( "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e ); } finally { connection.close(); } } @Override public boolean supportsAggressiveRelease() { return true; } @Override public boolean isUnwrappableAs(Class aClass) { return false; } @Override public <T> T unwrap(Class<T> aClass) { return null; } }

Y, finalmente, este es un LocalContainerEntityManagerFactoryBean conectado para hacer uso de los dos componentes anteriores:

@Configuration public class HibernateConfig { @Bean public JpaVendorAdapter jpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProvider multiTenantConnectionProvider, CurrentTenantIdentifierResolver tenantIdentifierResolver) { LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean(); emfBean.setDataSource(dataSource); emfBean.setPackagesToScan(VistoJobsApplication.class.getPackage().getName()); emfBean.setJpaVendorAdapter(jpaVendorAdapter()); Map<String, Object> jpaProperties = new HashMap<>(); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver); emfBean.setJpaPropertyMap(jpaProperties); return emfBean; } }

La fuente de datos que estoy usando está disponible automáticamente por Spring Boot .

Espero que esto ayude.


La sugerencia de utilizar <map> lugar de <props> parece funcionar para mí. https://jira.springsource.org/browse/SPR-10823#comment-94855

<bean id="multiTenantConnectionProvider" class="test.MultiTenantConnectionProviderImpl"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="packagesToScan" value="test.models" /> <property name="hibernateProperties"> <map> <entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect"/> <entry key="hibernate.multiTenancy" value="SCHEMA"/> <entry key="hibernate.tenant_identifier_resolver" value="test.CurrentTenantIdentifierResolverImpl"/> <entry key="hibernate.multi_tenant_connection_provider" value-ref="multiTenantConnectionProvider"/> </map> </property> </bean>


Ok, para terminar esto, esto es lo que terminé con lo siguiente. Yo uso un simple CurrentTenantIdentifierResolver. Y en lugar de intentar inyectar el DataSource desde otro lugar a mi MultiTenantConnectionProviderImpl, creo el DataSource (c3p0 ComboPooledDatasource) en el ConnectionProvider y comencé a usar solo las conexiones proporcionadas por mi ConnectionProvider. Así que eliminé el DataSource extra. Para que las propiedades de DataSource sean fácilmente configurables, opté por obtener los datos de configuración de un archivo de propiedades.

CurrentTenantIdentifierResolverImpl:

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver { /** * The method returns the RequestServerName as tenantidentifier. * If no FacesContext is available null is returned. * * @return String tenantIdentifier */ @Override public String resolveCurrentTenantIdentifier() { if (FacesContext.getCurrentInstance() != null){ return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName(); } else { return null; } } @Override public boolean validateExistingCurrentSessions() { return true; } }

MultiTenantConnectionProviderImpl:

Tenga en cuenta que el PropertyUtil es solo una clase de ayudante local simple para recuperar mis propiedades. Como no es nada especial, no lo incluiré para no desordenar la respuesta.

public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider { private static final long serialVersionUID = 8074002161278796379L; private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class ); private ComboPooledDataSource cpds; private Properties properties; /** * * Constructor. Initializes the ComboPooledDataSource based on the config.properties. * * @throws PropertyVetoException */ public MultiTenantConnectionProviderImpl() throws PropertyVetoException { log.info("Initializing Connection Pool!"); properties = new Properties(); try { properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties")); } catch (IOException e) { throw new RuntimeException(e); } cpds = new ComboPooledDataSource("Example"); cpds.setDriverClass(properties.getProperty("jdbc.driver")); cpds.setJdbcUrl(properties.getProperty("jdbc.url")); cpds.setUser(properties.getProperty("jdbc.user")); cpds.setPassword(PropertyUtil.getCredential("jdbc.password")); log.info("Connection Pool initialised!"); } @Override public Connection getAnyConnection() throws SQLException { log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}",new int[]{cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){ log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){ log.error("Connection pool empty!"); } return cpds.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { log.debug("Get {} Connection:::Number of connections (max: busy - idle): {} : {} - {}",new Object[]{tenantIdentifier, cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){ log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){ log.error("Connection pool empty!"); } return cpds.getConnection(tenantIdentifier, PropertyUtil.getCredential(tenantIdentifier)); } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection){ try { this.releaseAnyConnection(connection); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean supportsAggressiveRelease() { return false; } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType ); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if ( isUnwrappableAs( unwrapType ) ) { return (T) this; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } }

La configuración específica de c3p0 se toma de c3p0-config.xml :

<c3p0-config> <named-config name="Example"> <property name="acquireIncrement">3</property> <property name="preferredTestQuery">SELECT 1</property> <property name="checkoutTimeout">2000</property> <property name="idleConnectionTestPeriod">30</property> <property name="initialPoolSize">1</property> <property name="maxIdleTime">18000</property> <property name="maxPoolSize">30</property> <property name="minPoolSize">1</property> <property name="maxStatements">50</property> <property name="testConnectionOnCheckin">true</property> </named-config> </c3p0-config>

Y las propiedades específicas de db son proporcionadas por un archivo config.properties :

jdbc.url=<serverUrl> jdbc.driver=<driverClass> jdbc.dbName=<dBname> jdbc.dbowner=<dbo> jdbc.username=<user> jdbc.password=<password> hibernate.dialect=<hibernateDialect> hibernate.debug=false

Las credenciales se obtienen de forma similar de otro archivo.

Cualquier comentario que proporcione mejoras es apreciado.


Usando las respuestas de estos muchachos y este http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691 , puse esto juntos sin Spring ni nada más que C3P0.

Tuve que agregar estas 2 propiedades a mi configuración de hibernación

properties.setProperty("hibernate.multiTenancy", "SCHEMA"); properties.setProperty("hibernate.multi_tenant_connection_provider", MultiTenantConnectionProviderImpl.class.getName());

HibernateUtils.java

import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Alex */ public class HibernateUtils { private static final Logger logger = LoggerFactory.getLogger(HibernateUtils.class); private static SessionFactory sessionFactory; static{ init(); } public static void init(){ try { Configuration configuration = new Configuration() .setProperties(ConnectionPropertiesUtils.getProperties()); ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } catch (Exception e) { logger.error(e.getMessage()); } } public static Session getTenantSession(String tenant){ return getSession(tenant); } public static Session getAuthSession(){ return getSession("AUTH"); } public static Session getLogSession(){ return getSession("LOG"); } public static Session getConfigSession(){ return getSession("CONFIG"); } public static Session getSession(String tenant) throws HibernateException { if(sessionFactory == null){ init(); } return sessionFactory.withOptions().tenantIdentifier(tenant).openSession(); } @Deprecated public static Session getSession() throws HibernateException { if(sessionFactory == null){ init(); } return sessionFactory.openSession(); } }

Y MultiTenantConnectionProviderImpl.java

import com.mchange.v2.c3p0.ComboPooledDataSource; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import org.hibernate.HibernateException; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.service.spi.Stoppable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simplistic implementation for illustration purposes showing a single * connection pool used to serve multiple schemas using "connection altering". * Here we use the T-SQL specific USE command; Oracle users might use the ALTER * SESSION SET SCHEMA command; etc. */ public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable { private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class); private ComboPooledDataSource cpds; public MultiTenantConnectionProviderImpl() throws PropertyVetoException { log.info("Initializing Connection Pool!"); cpds = new ComboPooledDataSource("Example"); cpds.setDriverClass(ConnectionPropertiesUtils.getProperty("hibernate.connection.driver_class")); cpds.setJdbcUrl(ConnectionPropertiesUtils.getProperty("hibernate.connection.url")); cpds.setUser(ConnectionPropertiesUtils.getProperty("hibernate.connection.username")); cpds.setPassword(ConnectionPropertiesUtils.getProperty("hibernate.connection.password")); log.info("Connection Pool initialised!"); } @Override public Connection getAnyConnection() throws SQLException { log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}", new int[]{cpds.getMaxPoolSize(), cpds.getNumBusyConnectionsAllUsers(), cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()) { log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers() == 0) { log.error("Connection pool empty!"); } return cpds.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { //This is DB specific syntax. This work for MSSQL and MySQL //Oracle uses the ALTER SESSION SET SCHEMA command connection.createStatement().execute("USE " + tenantIdentifier); } catch (SQLException e) { throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) { try { this.releaseAnyConnection(connection); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean supportsAggressiveRelease() { return false; } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProviderImpl.class.isAssignableFrom(unwrapType); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if (isUnwrappableAs(unwrapType)) { return (T) this; } else { throw new UnknownUnwrapTypeException(unwrapType); } } public void stop() { cpds.close(); } }