2013-02-12 7 views
17

W aplikacji Spring 3 próbuję zaimplementować multi-tenancy za pomocą natywnych wersji MultiTenantConnectionProvider i CurrentTenantIdentifierResolver Hibernate 4. Widzę, że there was a problem with this in Hibernate 4.1.3, ale biegnę 4.1.9 i wciąż się podobny wyjątek:Multi-Tenancy z Spring + Hibernate: "SessionFactory skonfigurowany do wielokrotnego dzierżawy, ale nie określono identyfikatora dzierżawcy"

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 

Poniżej znajduje się odpowiedni kod. W MultiTenantConnectionProvider po prostu napisałem jakiś głupi kod, który za każdym razem zwraca nowe połączenie, a CurrentTenantIdentifierResolver zawsze zwraca ten sam identyfikator w tym miejscu. Oczywiście ta logika miała zostać wdrożona po tym, jak udało mi się uzyskać połączenia do utworzenia wystąpienia.

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; 
    } 

} 

Czy ktoś może zobaczyć coś specjalnie złego? Zgłasza wyjątek, gdy tylko transakcja zostanie otwarta. To wygląda na podobnie jak SessionFactory nie otwiera poprawnie sesji, lub Session po prostu ignoruje wartość zwróconą przez CurrentTenantIdentifierResolver, co moim zdaniem było problemem w Hibernate 4.1.3; to miało zostać rozwiązane.

Odpowiedz

10

Przedmowa: Mimo, że przyjąłem tę odpowiedź, która (będzie) zawierała kod, należy przegłosować Darren's answer, jeśli uważasz, że to było przydatne. On jest powodem, dla którego w ogóle mogłem to rozwiązać.

Ok, więc zaczynamy ....

As Darren pointed out, jest to naprawdę problem z SessionFactory na Sesji instancji niewłaściwie. Jeśli chcesz ręcznie utworzyć sesję, nie masz problemu. np:

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

Jednak @Transactional adnotacja powoduje SessionFactory otworzyć sesję z sessionFactory.getCurrentSession(), który nie pobiera identyfikator najemców z CurrentTenantIdentifierResolver.

Darren zasugerował otwarcie sesji ręcznie w warstwie DAO, ale oznacza to, że każda metoda DAO będzie miała transakcję o zasięgu lokalnym. Lepszym miejscem do tego jest warstwa serwisowa. Każde wywołanie warstwy usługi (tj. doSomeLogicalTask()) może wywoływać wiele metod DAO. Ma sens, aby każda z nich była związana z tą samą transakcją, ponieważ są one logicznie powiązane.

Co więcej, nie podobał mi się pomysł duplikowania kodu w każdej metodzie warstwy usługi w celu utworzenia transakcji i zarządzania nią. Zamiast tego użyłem AOP do zawijania każdej metody w mojej warstwie usług za pomocą porady, aby utworzyć instancję nowego Session i obsłużyć transakcję. Aspekt przechowuje bieżący Session w stosie TheadLocal, do którego można uzyskać dostęp przez warstwę DAO w celu odpytywania.

Cała ta praca pozwoli interfejsy i implementacje pozostać identyczne jak ich odpowiedniki utrwalonych błędów, z wyjątkiem jednej linii w nadrzędnej DAO że dostanie Session z ThreadLocal stosie aniżeli SessionFactory. Można to zmienić po naprawieniu błędu.

Wkrótce opublikuję kod, gdy go trochę posprzątam. Jeśli ktoś zauważy jakiekolwiek problemy z tym, nie krępuj się dyskutować poniżej.

+0

Mogę wiedzieć, że ten problem został rozwiązany w najnowszej najnowszej wersji hibernacji. Udostępnij, jeśli to możliwe, przykład działającego kodu. – VKPRO

+0

Testowany z Hibernate 4.2.6 działa. Problem rozwiązany w tej wersji. – VKPRO

+0

Czy mogę mieć twój przykład pracy proszę, używam również tego samego wzoru z @Transaction. –

0

Być może trzeba będzie uaktualnić wersję hibernacji do ostatniego 4.X i używać adnotacji lub aspekty, aby rozpocząć transakcję

+1

4.1.9 jest najnowszą stabilną wersją zapakowaną przez Maven. 4.2 jest dostępny jako CR, ale nawet z tym otrzymuję ten sam wyjątek. – Craige

14

Używasz @Transactional nigdzie w kodzie (np oznaczyć usługę lub dao klasy/metody)? Wystąpił ten sam błąd, dopóki nie skomentowałem @Transactional w mojej klasie usług. Myślę, że jest to związane z domyślnym zachowaniem klasy OpenSessionInThread w Hibernate 4.

Mam również skonfigurowany hibernate bez niestandardowej implementacji ConnectionProvider i TenantIdentifierResolver. Używam podejścia opartego na jndi, ustawiając hibernate.connection.datasource na java: // comp/env/jdbc /, a następnie przekazując nazwę zasobu jndi do moich metod dao, które wywołują sessionFactory.withOptions () .tenantIdentifier (najemca) .openSession();

Nadal się bawię, aby sprawdzić, czy mogę uzyskać konfigurację działającą z @Transactional, ale podejście oparte na jndi z domyślną sesją w wątku działa teraz.

+0

Używam @Transactional i myślę, że jesteś na miejscu ze swoją teorią. Czy możesz podać próbki kodu swojego rozwiązania Jndi? Zamierzam się z tym dzisiaj bawić i zobaczyć, czy mogę coś zrobić. – Craige

+0

Opracowałem rozwiązanie oparte na twoich opiniach tutaj. Nie jestem w 100% sprzedany, ale używam AOP do zawijania moich metod warstwy usług z ręczną obsługą transakcji. Dzięki temu publiczny interfejs mojej warstwy usług i klas DAO będzie czysty.Nadal używam ConnectionProvider i TenantIdentifierResolver, więc gdy ten problem zostanie załatany w przyszłości (co uważam za błąd), wszystkie moje interfejsy pozostają takie same i mogę po prostu usunąć aspekt zawijania mojej warstwy usługi. Wkrótce opublikuję odpowiedni kod. – Craige

+0

@ Craige czy udało ci się to sprawić? czy możesz udostępnić kod? –

2

Chociaż może to być starszy temat, a odpowiedź może być już zajęta. Co zauważyłem to:

W swojej zdefiniowania klasy CurrentTenantIdentifierResolverImpl:

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver 

Ale w config Państwo odwoływać MultiTenantIdentifierResolverImpl:

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

Wystarczy wskazując na to uwagę, ponieważ zrobiłem ten sam błąd dzisiaj, po tym wszystkim wszystko działało jak czar.

0

miałem podobny problem, kiedy moja realizacja CurrentTenantIdentifierResolver zwrócone null dla metody resolveCurrentTenantIdentifier()

0

Hibernate definiuje interfejs CurrentTenantIdentifierResolver pomóc ram jak wiosna lub Java EE w celu umożliwienia korzystania z mechanizmu instancji domyślnej Session (może to być od EntityManagerFactiry).

Tak więc, CurrentTenantIdentifierResolver musi być ustawiony przez właściwość konfiguracji, która jest dokładnie tam, gdzie poszło nie tak, ponieważ nie dostarczyłeś w pełni kwalifikowanej nazwy klasy. Realizacja CurrentTenantIdentifierResolver będąc CurrentTenantIdentifierResolverImpl The hibernate.tenant_identifier_resolver musi być:

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

Po rozwiązać ten problem, kiedy rozmowy HibernateTransactionManagergetSessionFactory().openSession(), Hibernate użyje CurrentTenantIdentifierResolverImpl rozwiązać identyfikator najemcy.

Powiązane problemy