有人知道如何访问数据源getConnection中的会话范围Bean吗?
我决定为每个客户端实现一个单独的数据库模式(多租户(,我发现了这篇非常有前途的文章:使用 Spring 和 PostgreSQL 的多租户,并且一直在尝试实现它所说的内容一段时间。
正如您在评论中看到的(我和其他人一样被困住了(,由于java.lang.IllegalStateException
,它不起作用.在最后一条评论中,还发现了这个问题,您可以在其中看到同样的事情。
作者(gargii(给出的答案是这样的:
错误消息可能是对您所处的状态的良好描述。试着想一想。专注于线程。哪个线程获取原始请求?哪个线程失败并出现错误?尝试调试 RequestContextHolder 类。这是实际完成会话魔术的地方(使用 ThreadLocal(。
井。。。我尝试调试但一无所获,我已经看到数十页试图解决问题,但仍然没有找到解决方案。
我总是得到这个异常(我在最后复制了完整的跟踪(:
2013-10-08 08:47:37,232 [localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [C:UserselyDocumentsDesarrolloSpringspringsourcevfabric-tc-server-developer-2.9.2.RELEASEbase-instancewtpwebappsTestMultiTenantWEB-INFclassesMETA-INFspringapplicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
简而言之,这就是我得到的:
先于其他任何内容执行的安全过滤器(applicationContext-Security.xml(:
<custom-filter before="FIRST" ref="tenantFilter" />
过滤器为:
public class TenantFilter implements Filter {
private TenantContext tenantContext;
private TenantRepository tenantRepository;
...
应用程序上下文.xml:
<bean id="tenantFilter" class="com.i4b.test1.core.TenantFilter">
<property name="tenantContext" ref="tenantContext" />
<property name="tenantRepository" ref="tenantRepository" />
</bean>
TenantContext 具有:
@Component
@RooJavaBean
@RooToString
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TenantContext {
private Tenant tenant;
private String idSchema;
}
(请注意,它不是RooEntity或RooActiveRecord。我只是使用 @RooJavaBean 和 @RooToString 来自动创建二传手、getter 和 toString(
在 web.xml 上进行强制配置时:
<!-- ADDED FOR MULTI TENANCY START -->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<!-- MULTI TENANCY END -->
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
和应用程序上下文.xml:
<bean id="tenantContext" class="com.i4b.test1.core.TenantContext" scope="session">
<aop:scoped-proxy />
</bean>
还有:
@Component
public class TenantAwareDataSource extends BasicDataSource {
@Autowired
private TenantContext tenantContext;
// [tenant,schema] to [DataSource] mapping
private final Map<String, BasicDataSource> schemaToDataSource = new HashMap<String, BasicDataSource>();
...
@Override
public Connection getConnection() throws SQLException {
BasicDataSource determineTargetDataSource = determineTargetDataSource();
return determineTargetDataSource.getConnection();
}
此类在应用程序上下文中配置.xml:
<bean class="com.i4b.test1.core.TenantAwareDataSource" destroy-method="close" id="dataSource">
<property name="driverClassName" value="${database.driverClassName}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="1800000"/>
<property name="numTestsPerEvictionRun" value="3"/>
<property name="minEvictableIdleTimeMillis" value="1800000"/>
<property name="validationQuery" value="SELECT version();"/>
</bean>
如果我从"TenantAwareDataSource"显示的代码中删除
@Autowired
,它不会给我异常,但实际上不起作用。 哈哈哈。 tenantContext 始终为 null,因为它不再是会话 Bean。
顺便一提。此应用程序是使用以下版本 (pom.xml( 创建的:
<properties>
<aspectj.version>1.7.2</aspectj.version>
<java.version>7</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<roo.version>1.2.4.RELEASE</roo.version>
<slf4j.version>1.7.5</slf4j.version>
<spring.version>3.2.3.RELEASE</spring.version>
<spring-security.version>3.1.0.RELEASE</spring-security.version>
</properties>
最后,完整的例外是:
2013-10-08 08:47:37,232 [localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [C:UserselyDocumentsDesarrolloSpringspringsourcevfabric-tc-server-developer-2.9.2.RELEASEbase-instancewtpwebappsTestMultiTenantWEB-INFclassesMETA-INFspringapplicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:741)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:389)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:294)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4887)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5381)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:633)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:657)
at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1637)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [C:UserselyDocumentsDesarrolloSpringspringsourcevfabric-tc-server-developer-2.9.2.RELEASEbase-instancewtpwebappsTestMultiTenantWEB-INFclassesMETA-INFspringapplicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1482)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:439)
at org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(BeanFactoryUtils.java:277)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.detectPersistenceExceptionTranslators(PersistenceExceptionTranslationInterceptor.java:139)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.<init>(PersistenceExceptionTranslationInterceptor.java:79)
at org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisor.<init>(PersistenceExceptionTranslationAdvisor.java:71)
at org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor.setBeanFactory(PersistenceExceptionTranslationPostProcessor.java:85)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeAwareMethods(AbstractAutowireCapableBeanFactory.java:1502)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1470)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
... 24 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:343)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:34)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.getTarget(CglibAopProxy.java:663)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:614)
at com.i4b.test1.core.TenantContext$$EnhancerByCGLIB$$c51ab566.getTenant(<generated>)
at com.i4b.test1.core.TenantContext_Roo_JavaBean.ajc$interMethodDispatch1$com_i4b_test1_core_TenantContext_Roo_JavaBean$com_i4b_test1_core_TenantContext$getTenant(TenantContext_Roo_JavaBean.aj)
at com.i4b.test1.core.TenantAwareDataSource.currentLookupKey(TenantAwareDataSource.java:77)
at com.i4b.test1.core.TenantAwareDataSource.determineTargetDataSource(TenantAwareDataSource.java:99)
at com.i4b.test1.core.TenantAwareDataSource.getConnection(TenantAwareDataSource.java:216)
at org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider.getConnection(InjectedDataSourceConnectionProvider.java:70)
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcServicesImpl.java:242)
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl.configure(JdbcServicesImpl.java:117)
at org.hibernate.service.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:75)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:159)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:131)
at org.hibernate.cfg.Configuration.buildTypeRegistrations(Configuration.java:1797)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1755)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:96)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:914)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:899)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:76)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:288)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1541)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1479)
... 39 more
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:90)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:329)
... 64 more `
您的DataSource
可能在发出任何请求之前很久就被实例化了(请注意日志中的引导线程名称 - [localhost-startStop-1]
(。所以实际上没有 Spring 可以使用的 SESSION 来满足 SESSION 范围。
您需要手动(通过RequestContextHolder
(实现会话范围或将组件耦合(例如通过ApplicationContext#getBean
(。
我们在几个项目上所做的是将TenantContext
实现为ThreadLocal
变量(类似于Spring Security的SecurityContext
(,该变量由一个特殊的servlet Filter
设置和取消设置。这是迄今为止最好的方法,因为没有组件会依赖于实际上存在活动 HTTP 请求的事实。
总而言之 - 对于您的特定用例,没有任何会话范围的 bean 会更好。
旁注:如果您使用的是 Hibernate,请注意已经支持多租户。其他框架也可能支持它。