Results 1 to 10 of 14

Thread: SessionFactory configured for multi-tenancy, but no tenant identifier specified

Hybrid View

  1. #1

    Default SessionFactory configured for multi-tenancy, but no tenant identifier specified

    Config:
    * Spring 3.1.1.RELEASE
    * Hibernate 4.1.0.FINAL
    * Commons-DBCP 1.4

    I'm trying to setup multi-tenancy with Hibernate 4.1 but it seems that SessionFactory isn't correctly configured by the transaction manager (or else).

    Code:
    	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
    		p:packagesToScan="com.company" 
    		>
    		<property name="hibernateProperties">
    			<props>
    				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLMyISAMDialect</prop>
    				<prop key="hibernate.show_sql">true</prop>
    				<prop key="hibernate.multiTenancy">DATABASE</prop>
    				<prop key="hibernate.tenant_identifier_resolver">com.mycompany.datasource.MultiTenantIdentifierResolver</prop>
    				<prop key="hibernate.multi_tenant_connection_provider">com.mycompany.datasource.MultiTenantConnectionProvider</prop>
    			</props>
    		</property>
    	</bean>
    
     	<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager" p:autodetectDataSource="false" p:sessionFactory-ref="sessionFactory"/>
    * I removed the p:dataSource-ref from LocalSessionFactoryBena because if I read correctly the documentation, it overrides the other settings.
    * Because of that, I also had to put "p:autodetectDataSource="false" in HibernateTransactionManager otherwise it tries to get the datasource & generates a NullPointerException
    * I finally removed the hibernate.current_session_context_class (previous set up as "jta") because it doesn't seem to change anything anymore with HibernateTransactionManager of hibernate4 package.

    So I have a simple MultiTenantConnectionProvider:

    Code:
    package com.mycompany.datasource;
    
    public class MultiTenantConnectionProvider extends org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider {
    	private static final long serialVersionUID = 1L;
    
    	@Override
    	public org.hibernate.service.jdbc.connections.spi.ConnectionProvider getAnyConnectionProvider() {
    		return MultiTenantConnectionHolder.getAnyConnectionProvider();
    	}
    
    	@Override
    	public org.hibernate.service.jdbc.connections.spi.ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
    		return MultiTenantConnectionHolder.getConnectionProvider(tenantIdentifier);
    	}
    }
    Whatever the details of MultiTenantConnectionHolder...

    Then I have my TenantIdentifierResolver implementation which seems to be loaded correctly:

    Code:
    package com.mycompany.datasource;
    
    public class MultiTenantIdentifierResolver implements org.hibernate.context.spi.CurrentTenantIdentifierResolver {
    
    	@Autowired
    	UserDetailsService userService;
    
    	public String resolveCurrentTenantIdentifier() {
    		return userService.getCurrentlyAuthenticatedUser().getTenantId();
    	}
    
    	public boolean validateExistingCurrentSessions() {
    		return true;
    	}
    }
    Then I have a webservice endpoint which is transactional

    Code:
    	@Transactional @ResponsePayload @PayloadRoot(localPart = "ActionRequest", namespace = NAMESPACE_URI)
    	public ActionResponse handleAWSRequest(@RequestPayload ActionRequest request) throws ActionRequestException {
    		...
    	}
    With all that, I get the exception "SessionFactory configured for multi-tenancy, but no tenant identifier specified".

    * I can see with AOP that the MultiTenantIdentifierResolver isn't called.
    * With the following stack trace, I can see that we are in a transaction context (HibernateTransactionManager.doBegin) but it can't pass the tenantIdentifier...

    Code:
    org.hibernate.HibernateException: SessionFactory configured for multi-tenancy, but no tenant identifier specified
    	at org.hibernate.internal.AbstractSessionImpl.<init>(AbstractSessionImpl.java:82)
    	at org.hibernate.internal.SessionImpl.<init>(SessionImpl.java:226)
    	at org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession(SessionFactoryImpl.java:1801)
    	at org.hibernate.internal.SessionFactoryImpl.openSession(SessionFactoryImpl.java:1009)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:191)
    	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:176)
    	at org.springframework.orm.hibernate4.SessionFactoryUtils.openSession(SessionFactoryUtils.java:114)
    	at org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:340)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    	at com.mycompany.webservice.endpoint.AWSEndPoint$$EnhancerByCGLIB$$f46141a4.handleAWSRequest(<generated>)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at org.springframework.ws.server.endpoint.MethodEndpoint.invoke(MethodEndpoint.java:132)
    	at org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter.invokeInternal(DefaultMethodEndpointAdapter.java:229)
    	at org.springframework.ws.server.endpoint.adapter.AbstractMethodEndpointAdapter.invoke(AbstractMethodEndpointAdapter.java:53)
    	at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:231)
    	at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:172)
    	at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:88)
    	at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:59)
    ......
    Searching for Hibernate implementation, I found in hibernate *SessionContext (JTA or Thread) the buildOrObtainSession() which calls baseSessionBuilder() which calls the tenantIdentifier, but obviously it's not going through this code. Probably because antother implementation is called...

    org.hibernate.context.spi.AbstractCurrentSessionCo ntext
    Code:
    	
    	protected SessionBuilder baseSessionBuilder() {
    		final SessionBuilder builder = factory.withOptions();
    		final CurrentTenantIdentifierResolver resolver = factory.getCurrentTenantIdentifierResolver();
    		if ( resolver != null ) {
    			builder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );
    		}
    		return builder;
    	}
    I'm quite lost with this...
    I feel like the usage of HibernateTransactionManager isn't using the Hibernate classes that supports the multi-tenancy configuration OR that it doesn't configure the sessionFactory with all parameters (TenantIdentifierResolver is this case)...
    Any help?

  2. #2
    Join Date
    Feb 2012
    Posts
    1

    Default

    I have the same issue. Obviously the CurrentTenantIdentifierResolver is only used by calling SessionFactory#getCurrentSession() (would only work with JTASessionContext and ThreadLocalSessionContext but not with SpringSessionContext, which is used in your configuration). But the TransactionManagers (at least the HibernateTransactionManager) call #openSession() instead of #getCurrentSession(), so the SessionContexts are never used by @Transactional.

    So the bug seems mainly to be in Hibernate instead of Spring. I think SpringSessionContext should use the CurrentTenantIdentifierResolver, but I guess this wouldn't solve the issue with @Transactional.

    I think the only solution would be to override HibernateTransactionManager, modify the TransactionInterceptor, don't use @Transactional or don't use CurrentTenantIdentifierResolver (so you also can't use @Transactional)
    Any other ideas?

  3. #3
    Join Date
    Oct 2010
    Posts
    4

    Default

    Hi,
    Have the same issue. Any workaround or suggested solution ?
    I found this : http://stackoverflow.com/questions/9...ng-3-hibernate
    Is it the only workaround ?

  4. #4

    Default

    I tried overiding the HibernateTransactionManager as suggested but it's quite difficult and couldn't get it to work.

    I don't know why, but no one at SpringSource has a clear explaination for this...

  5. #5
    Join Date
    Jul 2010
    Location
    Rome, Italy
    Posts
    5

    Default

    Few weeks ago I migrated a project from Hibernate 3 to Hibernate 4.
    Since it is a multi-user application I decided to test multy-tenancy of Hibernate 4 (with Hibernate 3 we adopted a custom solution).
    After this migration I decided to test Spring with this application and I found a lot of problems during the integration.
    I read a lot of threads here and on Stackoverflow and finally I found a solution.
    I want to share it with you, it is still embryonic and doesn't want to be the final solution of the problem rather an initial implementation to be improved.

    The solution is suited only when using annotations. It's due to the fact that multi-tenancy is a runtime concept.

    I created a custom annotation for the purpose.

    Code:
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Tenant {
    
    }

    And the advice:

    Code:
    @Aspect
    @Order(1)
    public class TenantAdvice {
    
    	@Autowired
    	@Qualifier("transactionManager")
    	MultiTenantHibernateTransactionManager transactionManager;
    	
    	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    	public void openSession() {
    	}
    
    	@Before("openSession()")
    	public void injectTenant(JoinPoint jp) {
    		MethodSignature signature = (MethodSignature) jp.getSignature();
    		Method method = signature.getMethod();
    		Annotation[][] paramAnnots = method.getParameterAnnotations();
    		Object[] args = jp.getArgs();
    		
    		int i = 0;
    		for (Annotation[] annotations : paramAnnots) {
    			Object arg = args[i++];
    			for (Annotation annotation : annotations) {
    				if (annotation instanceof Tenant) {
    					String tenantIdentifier = arg.toString();
    					transactionManager.setTenantIdentifier(tenantIdentifier);
    					break;
    				}
    			}
    		}
    	}
    }
    It observes every @Transactional method and look for annotated parameters to be set in the modified Transaction Manager with the new parameter tenantIdentifier.
    It's worth noting the order annotation. It's explained further in the post.

    The third and final class is the new Transaction Manager:

    Code:
    public class MultiTenantHibernateTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 6292693746575619760L;
    
    	public MultiTenantHibernateTransactionManager() {
    		super();
    	}
    
    	private static final Method getCurrentSessionMethod = ClassUtils.getMethod(SessionFactory.class, "getCurrentSession");
    	private SessionFactory sessionFactory;
    	private DataSource dataSource;
    	private boolean autodetectDataSource = true;
    	private boolean prepareConnection = true;
    	private boolean hibernateManagedSession = false;
    	private String tenantIdentifier = "";
    
    	public MultiTenantHibernateTransactionManager(SessionFactory sessionFactory) {
    		this.sessionFactory = sessionFactory;
    		afterPropertiesSet();
    	}
    
    	public void setSessionFactory(SessionFactory sessionFactory) {
    		this.sessionFactory = sessionFactory;
    	}
    
    	public SessionFactory getSessionFactory() {
    		return this.sessionFactory;
    	}
    
    	...
    	...
    	...
    
    	@Override
    	protected void doBegin(Object transaction, TransactionDefinition definition) {
    		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
    
    		if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
    			throw new IllegalTransactionStateException("Pre-bound JDBC Connection found! HibernateTransactionManager does not support "
    					+ "running within DataSourceTransactionManager if told to manage the DataSource itself. " + "It is recommended to use a single HibernateTransactionManager for all transactions "
    					+ "on a single DataSource, no matter whether Hibernate or JDBC access.");
    		}
    
    		Session session = null;
    
    		try {
    			if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
    				Session newSession = null;
    				// multi-tenant management
    				if (tenantIdentifier != null && !"".equals(tenantIdentifier)) {
    					SessionFactory sessionFactory = getSessionFactory();
    					SessionBuilder sb = sessionFactory.withOptions().tenantIdentifier(tenantIdentifier);
    					newSession = sb.openSession();
    					txObject.setSession(newSession);
    				} else {
    					newSession = SessionFactoryUtils.openSession(getSessionFactory());
    					txObject.setSession(newSession);
    				}
    				// end multi-tenant management
    
    				if (logger.isDebugEnabled()) {
    					logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
    				}
    			}
    
    			session = txObject.getSessionHolder().getSession();
    
    			if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
    				// We're allowed to change the transaction settings of the JDBC Connection.
    				if (logger.isDebugEnabled()) {
    					logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
    				}
    				Connection con = ((SessionImplementor) session).connection();
    				Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
    				txObject.setPreviousIsolationLevel(previousIsolationLevel);
    			} else {
    				// Not allowed to change the transaction settings of the JDBC Connection.
    				if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
    					// We should set a specific isolation level but are not allowed to...
    					throw new InvalidIsolationLevelException("HibernateTransactionManager is not allowed to support custom isolation levels: "
    							+ "make sure that its 'prepareConnection' flag is on (the default) and that the "
    							+ "Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default).");
    				}
    				if (logger.isDebugEnabled()) {
    					logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
    				}
    			}
    
    			if (definition.isReadOnly() && txObject.isNewSession()) {
    				// Just set to NEVER in case of a new Session for this transaction.
    				session.setFlushMode(FlushMode.MANUAL);
    			}
    
    			if (!definition.isReadOnly() && !txObject.isNewSession()) {
    				// We need AUTO or COMMIT for a non-read-only transaction.
    				FlushMode flushMode = session.getFlushMode();
    				if (FlushMode.isManualFlushMode(session.getFlushMode())) {
    					session.setFlushMode(FlushMode.AUTO);
    					txObject.getSessionHolder().setPreviousFlushMode(flushMode);
    				}
    			}
    
    			Transaction hibTx;
    
    			// Register transaction timeout.
    			int timeout = determineTimeout(definition);
    			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
    				// Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
    				// Applies to all statements, also to inserts, updates and deletes!
    				hibTx = session.getTransaction();
    				hibTx.setTimeout(timeout);
    				hibTx.begin();
    			} else {
    				// Open a plain Hibernate transaction without specified timeout.
    				hibTx = session.beginTransaction();
    			}
    
    			// Add the Hibernate transaction to the session holder.
    			txObject.getSessionHolder().setTransaction(hibTx);
    
    			// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
    			if (getDataSource() != null) {
    				Connection con = ((SessionImplementor) session).connection();
    				ConnectionHolder conHolder = new ConnectionHolder(con);
    				if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
    					conHolder.setTimeoutInSeconds(timeout);
    				}
    				if (logger.isDebugEnabled()) {
    					logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
    				}
    				TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
    				txObject.setConnectionHolder(conHolder);
    			}
    
    			// Bind the session holder to the thread.
    			if (txObject.isNewSessionHolder()) {
    				TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
    			}
    			txObject.getSessionHolder().setSynchronizedWithTransaction(true);
    		}
    
    		catch (Exception ex) {
    			if (txObject.isNewSession()) {
    				try {
    					if (session.getTransaction().isActive()) {
    						session.getTransaction().rollback();
    					}
    				} catch (Throwable ex2) {
    					logger.debug("Could not rollback Session after failed transaction begin", ex);
    				} finally {
    					SessionFactoryUtils.closeSession(session);
    				}
    			}
    			throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
    		}
    	}
    
    	
    	...
    	...
    	...
    
    
    	public String getTenantIdentifier() {
    		return tenantIdentifier;
    	}
    
    	public void setTenantIdentifier(String tenantIdentifier) {
    		this.tenantIdentifier = tenantIdentifier;
    	}
    
    	...
    	...
    	...
    
    }
    I highlighted the modified code and omitted unmodified one.

    Finally the configuration:

    Code:
    ...
    ...
    <tx:annotation-driven transaction-manager="transactionManager" order="2"/>
    ...
    ...
    <bean id="transactionManager" class="mypackage.MultiTenantHibernateTransactionManager">
    		<property name="sessionFactory" ref="sessionFactory" />
    		<property name="autodetectDataSource" value="false" />
    </bean>
    ...
    ...
    Transaction Manager is loaded with order 2 while the advice has order 1, it means the advice has a higher priority than the transaction manager.
    It is needed since tenantIdentifier needs to be read and set before transaction is started.

    Here it is my solution.
    Any suggestion, improvement... Or even insult is accepted and listened carefully

    Regards,
    Simone.

  6. #6

    Default

    Do you really need to override the HibernateTransactionManager? It should work without it BUT You need at least Hibernate 4.1.4 because there was a bug between 4.0 and 4.1.2 that make multitenant+spring not working. http://in.relation.to/Bloggers/HibernateORM414Release.

    I personaly used org.springframework.orm.hibernate4.LocalSessionFac toryBean depending on a MultiTenantConnectionHolder (custom class) that is based on a custom ConnectionProvider. The connection provider give the datasource to sessionFactory.

    hibernate.multiTenancy=DATABASE
    hibernate.tenant_identifier_resolver=com.......Mul tiTenantIdentifierResolver
    hibernate.multi_tenant_connection_provider=com.... ..MultiTenantConnectionProvider


    <bean id="transactionManager" class="org.springframework.orm.hibernate4.Hibernat eTransactionManager" p:autodetectDataSource="false" p:sessionFactory-ref="sessionFactory"/>

    And that's it.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •