1

有谁知道如何在 DataSource 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:\Users\ely\Documents\Desarrollo\Spring\springsource\vfabric-tc-server-developer-2.9.2.RELEASE\base-instance\wtpwebapps\TestMultiTenant\WEB-INF\classes\META-INF\spring\applicationContext.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 来自动创建 setter、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>

和 applicationContext.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();
}

此类在 applicationContext.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>

如果我@Autowired从“TenantAwareDataSource”显示的代码中删除,它不会给我异常,但实际上并没有工作。哈哈哈。tenantContext 始终为空,因为它不再是会话 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:\Users\ely\Documents\Desarrollo\Spring\springsource\vfabric-tc-server-developer-2.9.2.RELEASE\base-instance\wtpwebapps\TestMultiTenant\WEB-INF\classes\META-INF\spring\applicationContext.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:\Users\ely\Documents\Desarrollo\Spring\springsource\vfabric-tc-server-developer-2.9.2.RELEASE\base-instance\wtpwebapps\TestMultiTenant\WEB-INF\classes\META-INF\spring\applicationContext.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 `
4

1 回答 1

3

DataSource可能在发出任何请求之前很久就已实例化(请注意日志中的引导线程名称 - [localhost-startStop-1])。所以实际上没有 SESSION 可以被 Spring 用来满足 SESSION 范围。

您需要手动实现会话范围(通过RequestContextHolder)或使组件松散耦合(例如通过ApplicationContext#getBean)。


我们在几个项目中所做的是实现TenantContextThreadLocal变量(类似于 Spring Security 的SecurityContext),它由一个特殊的 servlet 设置和取消设置Filter。这是迄今为止最好的方法,因为没有任何组件会依赖于实际存在活动的 HTTP 请求这一事实。

总而言之 -对于您的特定用例,如果没有任何会话范围的 bean,您会更好。


旁注:如果您使用的是 Hibernate,请注意已经支持多租户。其他框架也可能支持它。

于 2013-10-08T19:33:06.057 回答