Multi-Tenant avec Spring + Hibernate: “SessionFactory configuré pour le multi-tenancy, mais aucun locataire n'identificateur spécifié”
Dans un Printemps 3 application, je suis en train de mettre en œuvre multi-tenancy via Hibernate 4 native de MultiTenantConnectionProvider et CurrentTenantIdentifierResolver. Je vois que il y avait un problème avec cette prolongée 4.1.3, mais je suis en cours d'exécution 4.1.9 et encore une exception similaire:
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
Ci-dessous est le code pertinent. Dans le MultiTenantConnectionProvider
j'ai simplement écrit un bête code pour aujourd'hui, qui renvoie simplement une nouvelle connexion à chaque fois, et le CurrentTenantIdentifierResolver
renvoie toujours le même ID à ce point. Évidemment, cette logique a été être mis en œuvre après que j'ai réussi à obtenir les connexions à instancier.
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;
}
}
Tout le monde peut voir quoi que ce soit de mal? Cela lève une exception dès qu'une transaction est ouvert. Il semble comme le SessionFactory
n'est pas l'ouverture de la Session correctement, ou de la Session
est tout simplement en ignorant la valeur retournée par la CurrentTenantIdentifierResolver
, qui je crois était le problème dans Hibernate 4.1.3; ce qui était censé avoir été résolu.
Vous devez vous connecter pour publier un commentaire.
Êtes-vous à l'aide de @Transactional n'importe où dans votre code (c'est à dire marquer un service ou d'une classe dao/méthode)? J'ai été en cours d'exécution dans la même erreur jusqu'à ce que j'ai commenté l' @Transactional dans ma classe de service. Je pense que c'est lié à la valeur par défaut openSessionInThread comportement de Hibernate 4.
J'ai aussi hibernate configuré sans personnalisé de mise en œuvre de la ConnectionProvider et TenantIdentifierResolver. Je suis en utilisant jndi-fondé de l'approche, la définition de la mise en veille prolongée.connexion.source de données en java://comp/env/jdbc/, puis en passant le nom de la ressource jndi dans mes méthodes dao, qui appellent sessionFactory.withOptions().tenantIdentifier(le locataire).openSession();
Je suis toujours de la partie pour voir si je peux obtenir une configuration de travail avec @Transactional, mais le jndi approche fondée sur la session par défaut dans le thread comportement semble fonctionner maintenant.
Avant-propos: Même si j'ai accepté cette réponse qui (sera) contient le code, veuillez upvote Darren réponse si vous pensez que c'est utile. Il est la raison pour laquelle j'ai été en mesure de résoudre ce à tous.
D'accord, alors nous y voilà....
Que Darren a souligné, c'est vraiment un problème avec SessionFactory est l'instanciation d'une Session de mal. Si vous étiez à instancier la session manuellement, vous n'avez pas de problème. par exemple:
Cependant, la
@Transactional
annotation causes de la SessionFactory pour ouvrir une session avecsessionFactory.getCurrentSession()
, qui ne tire pas le locataire de l'identificateur de laCurrentTenantIdentifierResolver
.Darren suggéré l'ouverture de la Session manuellement dans la couche DAO, mais cela signifie que chaque méthode DAO aura un localement étendue de la transaction. Le meilleur endroit pour le faire est sur la couche de service. Chaque couche de service d'appel (c'est à dire,
doSomeLogicalTask()
) peut faire appel à plusieurs méthodes DAO. Il est logique que chacun de ceux-ci devraient être liés à la même transaction, ils sont logiquement liées.De plus, je n'aimais pas l'idée de la duplication de code dans chaque service à la méthode de la couche de créer et de gérer une transaction. Au lieu de cela, j'ai utilisé de l'AOP pour envelopper chaque méthode dans ma couche service avec les conseils d'instancier un nouvel
Session
et gérer la transaction. L'aspect des magasins de l'actuelSession
dans unTheadLocal
pile qui peut être consulté par la couche DAO pour l'interrogation.L'ensemble de ces travaux permettra les interfaces et implémentations de rester identique à leur bogue-fixe homologues, à l'exception d'une ligne dans le DAO de la superclasse qui obtiendra le
Session
de laThreadLocal
pile plutôt que de laSessionFactory
. Ceci peut être modifié une fois que le bug est corrigé.Je vais poster le code de peu de temps, une fois que j'ai nettoyer un peu. Si quelqu'un voit aucun problème avec cette fonction, n'hésitez pas à discuter ci-dessous.
Même si cela pourrait être un ancien sujet, et la réponse sera peut-être déjà prises en charge. Ce que j'ai remarqué est la suivante:
Dans vos définir la classe CurrentTenantIdentifierResolverImpl:
Mais dans la config de référence MultiTenantIdentifierResolverImpl:
Simplement en pointant ce parce que j'ai fait la même erreur aujourd'hui, après que tout a fonctionné comme un charme.
Comme je l'ai expliqué dans cet article, Hibernate définit la
CurrentTenantIdentifierResolver
interface à l'aide des frameworks comme Spring ou Java EE pour permettre l'utilisation de la valeur par défautSession
mécanisme d'instanciation (que ce soit à partir d'unEntityManagerFactiry
).Donc, le
CurrentTenantIdentifierResolver
doit être défini par l'intermédiaire d'une propriété de configuration qui est exactement où vous êtes allé mal parce que vous n'avez pas le droit complet de la classe nom. LeCurrentTenantIdentifierResolver
mise en œuvreCurrentTenantIdentifierResolverImpl
, lehibernate.tenant_identifier_resolver
doit être:Après avoir corrigé cela, quand le
HibernateTransactionManager
appelsgetSessionFactory().openSession()
, Hibernate utilisera leCurrentTenantIdentifierResolverImpl
pour résoudre le locataire de l'identificateur.Peut-être vous avez besoin de mettre à niveau la version d'hibernate pour les 4 derniers.X et utiliser les annotations ou les aspects de lancer la transaction
J'ai eu un problème similaire lors de mon CurrentTenantIdentifierResolver mise en œuvre retourné null pour le resolveCurrentTenantIdentifier() la méthode