2013-02-12 5 views
17

In un'applicazione Spring 3, sto cercando di implementare multi-tenancy tramite il numero nativo di Hibernate 4 MultiTenantConnectionProvider e CurrentTenantIdentifierResolver. Vedo che there was a problem with this in Hibernate 4.1.3, ma sto correndo 4.1.9 e ancora ottenere un'eccezione simile:Multi-tenancy con Spring + Hibernate: "SessionFactory configurato per multi-tenancy, ma nessun identificatore di titolare specificato"

Caused by: 

org.hibernate.HibernateException: SessionFactory configured for multi-tenancy, but no tenant identifier specified 
    at org.hibernate.internal.AbstractSessionImpl.<init>(AbstractSessionImpl.java:84) 
    at org.hibernate.internal.SessionImpl.<init>(SessionImpl.java:239) 
    at org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession(SessionFactoryImpl.java:1597) 
    at org.hibernate.internal.SessionFactoryImpl.openSession(SessionFactoryImpl.java:963) 
    at org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:328) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371) 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:334) 
    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.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631) 
    at com.afflatus.edu.thoth.repository.UserRepository$$EnhancerByCGLIB$$c844ce96.getAllUsers(<generated>) 
    at com.afflatus.edu.thoth.service.UserService.getAllUsers(UserService.java:29) 
    at com.afflatus.edu.thoth.HomeController.hello(HomeController.java:37) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:601) 
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219) 
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132) 
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) 
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:746) 
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:687) 
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80) 
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) 
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) 
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:915) 
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:811) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:735) 
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:796) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848) 
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:671) 
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:448) 
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:138) 
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:564) 
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:213) 
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1070) 
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:375) 
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:175) 
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1004) 
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:136) 
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:258) 
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:109) 
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97) 
    at org.eclipse.jetty.server.Server.handle(Server.java:439) 
    at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:246) 
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:265) 
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240) 
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:589) 
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:520) 
    at java.lang.Thread.run(Thread.java:722) enter code here 

Di seguito si riporta il codice relativo. Nel MultiTenantConnectionProvider ho semplicemente scritto del codice stupido per ora che restituisce sempre una nuova connessione ogni volta e lo CurrentTenantIdentifierResolver restituisce sempre lo stesso ID a questo punto. Ovviamente questa logica doveva essere implementata dopo che sono riuscito a ottenere le connessioni per creare un'istanza.

config.xml

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="packagesToScan"> 
     <list> 
      <value>com.afflatus.edu.thoth.entity</value> 
     </list> 
    </property> 
    <property name="hibernateProperties"> 
     <props> 
      <prop key="hibernate.dialect">${hibernate.dialect}</prop> 
      <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> 
      <prop key="hibernate.hbm2ddl">${hibernate.dbm2ddl}</prop> 
      <prop key="hibernate.multiTenancy">DATABASE</prop> 
      <prop key="hibernate.multi_tenant_connection_provider">com.afflatus.edu.thoth.connection.MultiTenantConnectionProviderImpl</prop> 
      <prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop> 
     </props> 
    </property> 
</bean> 

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> 
    <property name="autodetectDataSource" value="false" /> 
    <property name="sessionFactory" ref="sessionFactory" /> 
</bean> 

MultiTenantConnectionProvider.java

package com.afflatus.edu.thoth.connection; 

import java.util.Properties; 
import java.util.HashMap; 
import java.util.Map; 

import org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider; 
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; 
import org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider; 
import org.springframework.jdbc.datasource.DriverManagerDataSource; 
import org.hibernate.cfg.*; 

public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider { 

    private final Map<String, ConnectionProvider> connectionProviders 
     = new HashMap<String, ConnectionProvider>(); 

    @Override 
    protected ConnectionProvider getAnyConnectionProvider() { 

     System.out.println("barfoo"); 
     Properties properties = getConnectionProperties(); 

     DriverManagerDataSource ds = new DriverManagerDataSource(); 
     ds.setDriverClassName("com.mysql.jdbc.Driver"); 
     ds.setUrl("jdbc:mysql://127.0.0.1:3306/test"); 
     ds.setUsername("root"); 
     ds.setPassword(""); 

     InjectedDataSourceConnectionProvider defaultProvider = new InjectedDataSourceConnectionProvider(); 
     defaultProvider.setDataSource(ds); 
     defaultProvider.configure(properties); 

     return (ConnectionProvider) defaultProvider; 
    } 


    @Override 
    protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { 
     System.out.println("foobar"); 
     Properties properties = getConnectionProperties(); 

     DriverManagerDataSource ds = new DriverManagerDataSource(); 
     ds.setDriverClassName("com.mysql.jdbc.Driver"); 
     ds.setUrl("jdbc:mysql://127.0.0.1:3306/test2"); 
     ds.setUsername("root"); 
     ds.setPassword(""); 

     InjectedDataSourceConnectionProvider defaultProvider = new InjectedDataSourceConnectionProvider(); 
     defaultProvider.setDataSource(ds); 
     defaultProvider.configure(properties); 

     return (ConnectionProvider) defaultProvider; 
    } 

    private Properties getConnectionProperties() { 
     Properties properties = new Properties(); 
     properties.put(AvailableSettings.DIALECT, "org.hibernate.dialect.MySQLDialect"); 
     properties.put(AvailableSettings.DRIVER, "com.mysql.jdbc.Driver"); 
     properties.put(AvailableSettings.URL, "jdbc:mysql://127.0.0.1:3306/test"); 
     properties.put(AvailableSettings.USER, "root"); 
     properties.put(AvailableSettings.PASS, ""); 

     return properties; 

    } 
} 

CurrentTenantIdentifierResolver.java

package com.afflatus.edu.thoth.context; 

import org.hibernate.context.spi.CurrentTenantIdentifierResolver; 

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver { 

    public String resolveCurrentTenantIdentifier() { 
     return "1"; 
    } 

    public boolean validateExistingCurrentSessions() { 
     return true; 
    } 

} 

Qualcuno può vedere qualcosa di specifico che non va? Ciò genera un'eccezione non appena viene aperta una transazione. Lo sembra come lo SessionFactory non sta aprendo correttamente la Sessione, o lo Session sta semplicemente ignorando il valore restituito dallo CurrentTenantIdentifierResolver, che credo fosse il problema in Hibernate 4.1.3; questo avrebbe dovuto essere risolto.

risposta

10

Prefazione: Sebbene abbia accettato questa risposta che contiene il codice, si prega di aumentare di valore lo Darren's answer se si ritiene che ciò sia stato utile. È la ragione per cui sono stato in grado di risolvere tutto questo.

Okay, qui andiamo ....

As Darren pointed out, questo è davvero un problema con la SessionFactory di un'istanza di una sessione in modo improprio. Se dovessi creare un'istanza manuale della sessione, non avrai alcun problema. ad esempio:

sessionFactory.withOptions().tenantIdentifier(tenant).openSession(); 

Tuttavia, l'annotazione @Transactional fa sì che la SessionFactory di aprire una sessione con sessionFactory.getCurrentSession(), che non tira l'identificatore inquilino dal CurrentTenantIdentifierResolver.

Darren ha suggerito di aprire manualmente la sessione nel livello DAO, ma ciò significa che ogni metodo DAO avrà una transazione con ambito locale. Il posto migliore per farlo è sul livello di servizio. Ogni chiamata del livello di servizio (ad esempio, doSomeLogicalTask()) può chiamare più metodi DAO. Ha senso che ognuno di questi debba essere associato alla stessa transazione, in quanto sono correlati logicamente.

Inoltre, non mi piaceva l'idea di duplicare il codice in ogni metodo del livello di servizio per creare e gestire una transazione. Invece, ho usato AOP per avvolgere ogni metodo nel mio livello di servizio con il consiglio di creare un'istanza di un nuovo Session e gestire la transazione. L'aspetto memorizza l'attuale Session in uno stack TheadLocal a cui è possibile accedere dal livello DAO per l'esecuzione di query.

Tutto questo lavoro consentirà le interfacce e le implementazioni per rimanere identiche alle loro controparti bug-fisso, tranne una riga nella superclasse DAO che otterrà il Session dal ThreadLocal pila piuttosto che il SessionFactory. Questo può essere cambiato una volta risolto il problema.

Inserirò il codice poco dopo averlo pulito un po '. Se qualcuno vede qualche problema con questo, non esitate a discutere di seguito.

+0

Posso sapere che questo problema è stato risolto nell'ultima versione di ibernazione corrente. Condividi, se possibile, il tuo esempio di codice di lavoro. – VKPRO

+0

Testato con Hibernate 4.2.6 funziona ora. Il problema è stato risolto in questa versione. – VKPRO

+0

Posso avere il tuo esempio di lavoro per favore, sto anche usando lo stesso modello con @Transaction. –

0

Forse è necessario aggiornare la versione di Hibernate all'ultimo 4.X e utilizzare annotazione o aspetti per avviare la transazione

+1

4.1.9 è l'ultima versione stabile fornita da Maven. 4.2 è disponibile come CR, ma anche con questo ottengo la stessa eccezione. – Craige

14

Stai usando @Transactional qualsiasi punto del codice (ad esempio contrassegnare un servizio o dao classe/metodo)? Stavo correndo lo stesso errore fino a quando ho commentato @Transactional nella mia classe di servizio. Penso che sia correlato al comportamento predefinito di openSessionInThread di Hibernate 4.

Ho anche configurato l'ibernazione senza un'implementazione personalizzata di ConnectionProvider e TenantIdentifierResolver. Sto usando l'approccio basato su jndi, impostando hibernate.connection.datasource su java: // comp/env/jdbc /, e poi passando il nome della risorsa jndi nei miei metodi dao, che chiamano sessionFactory.withOptions () .tenantIdentifier (inquilino) .openSession();

Sto ancora giocando per vedere se riesco a ottenere una configurazione funzionante con @Transactional, ma l'approccio basato su jndi con la sessione predefinita nel comportamento del thread sembra funzionare ora.

+0

Sto usando @Transactional e penso che tu sia azzeccato con la tua teoria. Potete fornire alcuni esempi di codice della vostra soluzione Jndi? Oggi giocherò con questo e vedrò se riesco a far funzionare qualcosa. – Craige

+0

Ho elaborato una soluzione in base al tuo feedback qui. Non sono venduto al 100%, ma sto usando AOP per avvolgere i miei metodi del livello di servizio con la gestione manuale delle transazioni. Ciò manterrà l'interfaccia pubblica del mio livello di servizio e le classi DAO pulite.Sto ancora utilizzando ConnectionProvider e TenantIdentifierResolver, quindi quando questo problema viene risolto in futuro (che credo sia un bug), tutte le mie interfacce rimangono le stesse e posso semplicemente rimuovere l'aspetto che avvolge il mio livello di servizio. Pubblicherò presto il codice pertinente. – Craige

+0

@Craige sei riuscito a farlo funzionare? puoi condividere il codice? –

2

Anche se questo potrebbe essere un argomento precedente, e la risposta potrebbe essere già presa in considerazione. Quello che ho notato è la seguente:

Nella tua definire la CurrentTenantIdentifierResolverImpl classe:

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver 

Ma nella configurazione si fa riferimento MultiTenantIdentifierResolverImpl:

<prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop> 

Proprio la precisazione perché ho fatto lo stesso errore oggi, dopo che tutto ha funzionato come un fascino.

0

Ho avuto un problema simile quando mia implementazione CurrentTenantIdentifierResolver restituito nulla per il metodo resolveCurrentTenantIdentifier()

0

Hibernate definisce l'interfaccia CurrentTenantIdentifierResolver per aiutare framework come Spring o Java EE per consentire l'utilizzo del meccanismo di un'istanza di default Session (sia esso da un EntityManagerFactiry).

Quindi, il CurrentTenantIdentifierResolver deve essere impostato tramite una proprietà di configurazione che è esattamente dove si è andato storto perché non è stato fornito il giusto nome di classe completo. L'implementazione CurrentTenantIdentifierResolver essendo CurrentTenantIdentifierResolverImpl, il hibernate.tenant_identifier_resolver deve essere:

<prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.CurrentTenantIdentifierResolverImpl</prop> 

Dopo aver risolto questo, quando le chiamate HibernateTransactionManagergetSessionFactory().openSession(), Hibernate userà la CurrentTenantIdentifierResolverImpl per risolvere l'identificatore inquilino.