在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$3.run(QueuedThreadPool.java:520) at java.lang.Thread.run(Thread.java:722) enter code here
以下是相关代码。在MultiTenantConnectionProviderI中,我现在只写了一些哑代码,每次都只返回一个新的连接,并且此时CurrentTenantIdentifierResolver总是返回相同的ID。显然,在我设法实例化连接之后,将实施此逻辑。
MultiTenantConnectionProvider
CurrentTenantIdentifierResolver
<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>
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; } }
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; } }
有人能看到特别明显的错误吗?一旦打开事务,这将引发异常。它 看起来 像SessionFactory没有被正确打开会话,或者Session是简单地忽略了由返回的值CurrentTenantIdentifierResolver,我相信是在Hibernate中4.1.3的问题; 这应该已经解决了。
SessionFactory
Session
前瞻性:尽管我接受了(将)包含代码 的答案,但如果您认为这很有用,请提高Darren的答案。他是我完全能够解决此问题的原因。
好吧,我们开始....
正如Darren所指出的,这确实是SessionFactory错误地实例化Session的问题。如果要手动实例化会话,则没有问题。例如:
sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
但是,@Transactional注释会导致SessionFactory用打开会话sessionFactory.getCurrentSession(),这不会从中拉出租户标识符CurrentTenantIdentifierResolver。
@Transactional
sessionFactory.getCurrentSession()
Darren建议在DAO层中手动打开Session,但这意味着每种DAO方法都将具有本地范围的事务。更好的方法是在服务层上。每个服务层调用(即doSomeLogicalTask())可以调用多个DAO方法。有意义的是,每个逻辑上都应绑定到同一事务。
doSomeLogicalTask()
此外,我不喜欢在每个服务层方法中复制代码来创建和管理事务的想法。相反,我使用AOP将每个方法包装在服务层中,并带有实例化新方法Session和处理事务的建议。方面将电流存储Session在TheadLocal堆栈中,DAO层可以访问该堆栈以进行查询。
TheadLocal
所有这些工作都将使接口和实现与错误修复的接口和实现保持相同,除了DAO超类中的一行Session将从ThreadLocal堆栈中获取而不是从中获取SessionFactory。修复错误后即可更改。
ThreadLocal
一旦整理了一下,我将很快发布代码。如果有人对此有任何疑问,请随时在下面进行讨论。