java Spring + Hibernate 的多租户:“为多租户配置了 SessionFactory,但未指定租户标识符”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14837601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Multi-Tenancy with Spring + Hibernate: "SessionFactory configured for multi-tenancy, but no tenant identifier specified"
提问by Craige
In a Spring 3 application, I'm trying to implement multi-tenancy via Hibernate 4's native MultiTenantConnectionProviderand CurrentTenantIdentifierResolver. I see that there was a problem with this in Hibernate 4.1.3, but I'm running 4.1.9 and still getting a similar exception:
在 Spring 3 应用程序中,我试图通过 Hibernate 4 的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我看到在 Hibernate 4.1.3中存在这个问题,但我正在运行 4.1.9 并且仍然遇到类似的异常:
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.run(QueuedThreadPool.java:520)
at java.lang.Thread.run(Thread.java:722) enter code here
Below is the relevant code. In the MultiTenantConnectionProvider
I've simply wrote some dumb code for now that just returns a new connection every time, and the CurrentTenantIdentifierResolver
always returns the same ID at this point. Obviously this logic was to be implemented after I managed to get the connections to instantiate.
下面是相关代码。在MultiTenantConnectionProvider
我现在只是写了一些愚蠢的代码,每次只返回一个新连接,此时CurrentTenantIdentifierResolver
总是返回相同的 ID。显然,这个逻辑是在我设法让连接实例化之后实现的。
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
多租户连接提供者.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
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;
}
}
Can anybody see anything specifically wrong? This throws an exception as soon as a transaction is opened. It seemslike the SessionFactory
isn't opening the Session correctly, or the Session
is simply ignoring the value returned by the CurrentTenantIdentifierResolver
, which I believe was the issue in Hibernate 4.1.3; this was supposed to have been resolved.
有人能看出什么特别错误吗?这会在事务打开后立即引发异常。它看起来像SessionFactory
没有被正确打开会话,或者Session
是简单地忽略了由返回的值CurrentTenantIdentifierResolver
,我相信是在Hibernate中4.1.3的问题; 这应该已经解决了。
采纳答案by Craige
Foreward: Although I accepted this answer which (will) contains code, please upvote Darren's answerif you think this was useful. He's the reason I was able to solve this at all.
前言:虽然我接受了这个(将)包含代码的答案,但如果您认为这有用,请为Darren 的答案点赞。他是我能够解决这个问题的原因。
Okay, so here we go....
好的,所以我们开始......
As Darren pointed out, this is really an issue with SessionFactory's instantiating a Session improperly. If you were to instantiate the session manually, you have no issue. eg:
正如 Darren 指出的那样,这确实是 SessionFactory 不正确地实例化 Session 的一个问题。如果您要手动实例化会话,则没有问题。例如:
sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
However, the @Transactional
annotation causes the SessionFactory to open a session with sessionFactory.getCurrentSession()
, which does not pull the tenant identifier from the CurrentTenantIdentifierResolver
.
但是,@Transactional
注释会导致 SessionFactory 使用 开启一个会话sessionFactory.getCurrentSession()
,这不会从 中提取租户标识符CurrentTenantIdentifierResolver
。
Darren suggested opening the Session manually in the DAO layer, but this means that each DAO method will have a locally scoped transaction. The better place to do this is on the service layer. Each service layer call (ie, doSomeLogicalTask()
) may call multiple DAO methods. It makes sense that each of these should be bound to the same transaction, as they're logically related.
Darren 建议在 DAO 层手动打开 Session,但这意味着每个 DAO 方法都会有一个本地范围的事务。最好的地方是在服务层。每个服务层调用(即doSomeLogicalTask()
)可能会调用多个 DAO 方法。每一个都应该绑定到同一个事务是有道理的,因为它们在逻辑上是相关的。
Furthermore, I didn't like the idea of duplicating code in each service layer method to create and manage a transaction. Instead, I used AOP to wrap each method in my service layer with with the advice to instantiate a new Session
and handle the transaction. The aspect stores the current Session
in a TheadLocal
stack which can be accessed by the DAO layer for querying.
此外,我不喜欢在每个服务层方法中复制代码来创建和管理事务的想法。相反,我使用 AOP 将每个方法包装在我的服务层中,并提供建议以实例化一个新的Session
并处理事务。方面将当前存储Session
在TheadLocal
堆栈中,DAO 层可以访问该堆栈以进行查询。
All of this work will allow the interfaces and implementations to stay identical to their bug-fixed counterparts, except one line in the DAO superclass that will get the Session
from the ThreadLocal
stack rather than the SessionFactory
. This can be changed once the bug is fixed.
除了 DAO 超类中的一行Session
将从ThreadLocal
堆栈中获取SessionFactory
. 修复错误后,可以更改此设置。
I will post the code shortly, once I clean it up a little. If anybody sees any problems with this, do feel free to discuss below.
一旦我稍微清理一下,我将很快发布代码。如果有人看到这方面的任何问题,请随时在下面讨论。
回答by Darren
Are you using @Transactional anywhere in your code (ie mark a service or dao class/method)? I was running into the same error until I commented out the @Transactional in my service class. I think it's related to the default openSessionInThread behavior of Hibernate 4.
您是否在代码中的任何地方使用 @Transactional(即标记服务或 dao 类/方法)?在我注释掉服务类中的 @Transactional 之前,我遇到了同样的错误。我认为这与 Hibernate 4 的默认 openSessionInThread 行为有关。
I also have hibernate configured without a custom implementation of the ConnectionProvider and TenantIdentifierResolver. I'm using the jndi-based approach, setting the hibernate.connection.datasource to java://comp/env/jdbc/, and then passing in the name of the jndi resource into my dao methods, which call sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
我还配置了休眠,但没有自定义 ConnectionProvider 和 TenantIdentifierResolver 的实现。我正在使用基于 jndi 的方法,将 hibernate.connection.datasource 设置为 java://comp/env/jdbc/,然后将 jndi 资源的名称传递到我的 dao 方法中,该方法调用 sessionFactory.withOptions( ).tenantIdentifier(tenant).openSession();
I'm still playing around to see if I can get a configuration working with @Transactional, but the jndi-based approach with the default session in thread behavior seems to be working now.
我仍在尝试查看是否可以使用@Transactional 进行配置,但是在线程行为中使用默认会话的基于 jndi 的方法现在似乎正在运行。
回答by Vlad Mihalcea
As I explained in this article, Hibernate defines the CurrentTenantIdentifierResolver
interface to help frameworks like Spring or Java EE to allow using the default Session
instantiation mechanism (be it from an EntityManagerFactiry
).
正如我在本文中所解释的,Hibernate 定义了CurrentTenantIdentifierResolver
接口来帮助 Spring 或 Java EE 等框架允许使用默认的Session
实例化机制(无论是来自EntityManagerFactiry
)。
So, the CurrentTenantIdentifierResolver
must be set via a configuration property which is exactly where you went wrong because you didn't supply the right fully-qualified class name. The CurrentTenantIdentifierResolver
implementation being CurrentTenantIdentifierResolverImpl
, the hibernate.tenant_identifier_resolver
has to be:
因此,CurrentTenantIdentifierResolver
必须通过配置属性进行设置,这正是您出错的地方,因为您没有提供正确的完全限定类名。在CurrentTenantIdentifierResolver
实施之中CurrentTenantIdentifierResolverImpl
,将hibernate.tenant_identifier_resolver
必须是:
<prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.CurrentTenantIdentifierResolverImpl</prop>
After you fix this, when the HibernateTransactionManager
calls getSessionFactory().openSession()
, Hibernate will use the CurrentTenantIdentifierResolverImpl
to resolve the tenant identifier.
修复此问题后,当HibernateTransactionManager
调用 时getSessionFactory().openSession()
,Hibernate 将使用CurrentTenantIdentifierResolverImpl
来解析租户标识符。
回答by Carsten
Even though this might be an older topic, and the answer might be already taken care of. What I noticed is the following:
即使这可能是一个较旧的主题,并且答案可能已经得到解决。我注意到的是以下内容:
In your define the class CurrentTenantIdentifierResolverImpl:
在您定义类 CurrentTenantIdentifierResolverImpl 中:
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver
But in your config you reference MultiTenantIdentifierResolverImpl:
但是在您的配置中,您引用了 MultiTenantIdentifierResolverImpl:
<prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop>
Just pointing this out because I did the same mistake today, after that it all worked like a charm.
只是指出这一点,因为我今天犯了同样的错误,之后一切都像魅力一样。
回答by Scott Warren
I had a similar issue when my CurrentTenantIdentifierResolver implementation returned null for the resolveCurrentTenantIdentifier() method
当我的 CurrentTenantIdentifierResolver 实现为 resolveCurrentTenantIdentifier() 方法返回 null 时,我遇到了类似的问题
回答by David
Perhaps you need to upgrade version of hibernate to last 4.X and use annotation or aspects to start transaction
也许您需要将 hibernate 的版本升级到最新的 4.X 并使用注解或方面来启动事务