弹簧。在我的CustomDataSource getConnection方法中访问会话Bean

时间:2013-10-08 19:08:38

标签: java spring spring-mvc multi-tenant session-bean

有人知道如何访问DataSource getConnection中的会话范围bean吗?

我决定为每个客户端实现一个单独的数据库架构(多租户),我找到了这篇非常有前途的文章:Multitenancy using Spring and PostgreSQL,并且一直试图实现它所说的内容。

正如你在评论中看到的那样(我和其他人一样被困住),由于java.lang.IllegalStateException而无效。在最后一条评论中也找到了this question,您可以在其中看到相同的内容。

作者(gargii)给出的答案是:

  

错误信息可能是您所处状态的一个非常好的解释。试着想一想。专注于线程。哪个线程获得原始请求?哪个线程因错误而失败?尝试调试RequestContextHolder类。这是实际完成session-magic的地方(使用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;
...

的applicationContext.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,getters和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>
  

如果我从“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:\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 `

1 个答案:

答案 0 :(得分:3)

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

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


我们在几个项目上所做的是将TenantContext实现为ThreadLocal变量(类似于Spring Security的SecurityContext),该变量由特殊的servlet {{1设置和设置}}。这是迄今为止最好的方法,因为没有任何组件会依赖于实际存在活动HTTP请求的事实。

总结一下 - 对于您的特定用例,没有任何会话范围的bean 会更好。


旁注:如果您使用的是Hibernate,请注意已经存在support for multi-tenancy。其他框架也可能支持它。