在休眠多租户应用程序中启动时不查找数据源

Posted

技术标签:

【中文标题】在休眠多租户应用程序中启动时不查找数据源【英文标题】:No lookup on start for datasources in hibernate multi-tenant application 【发布时间】:2015-01-27 00:25:48 【问题描述】:

我目前正在开发一个多租户应用程序使用 Spring 和 Hibernate 以及单独的数据库。 我希望即使我的一个数据源无法访问(以防出现问题)我的应用程序也能正常工作,以便其他租户仍然可以访问他们的数据。这就是为什么我不希望我的应用程序在部署时检查数据源。我试图告诉不要在开始时查找数据源:

applicationContext-datasources.xml :

<!-- data source 1 -->
<jee:jndi-lookup id="ds1" jndi-name="jdbc/ds1" resource-ref="true" lookup-on-startup="false" expected-type="javax.sql.DataSource" />

<!-- data source 2 -->
<jee:jndi-lookup id="ds2" jndi-name="jdbc/ds2" resource-ref="true" lookup-on-startup="false" expected-type="javax.sql.DataSource" />

但我得到了这个异常堆栈:

11:24:10,192 INFO  [STDOUT] 2014-11-28 11:24:10,189 - ERROR [main (ContextLoader.java:307) - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [applicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.jndi.JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object; nested exception is javax.naming.NameNotFoundException: jdbc not bound
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [applicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.jndi.JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object; nested exception is javax.naming.NameNotFoundException: jdbc not bound
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:342)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
...
Caused by: org.springframework.jndi.JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object; nested exception is javax.naming.NameNotFoundException: jdbc not bound
at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:139)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:182)
at $Proxy54.getConnection(Unknown Source)
at com.example.MultiTenantConnectionProviderImpl.getConnection(MultiTenantConnectionProviderImpl.java:45)
at com.example.MultiTenantConnectionProviderImpl.getAnyConnection(MultiTenantConnectionProviderImpl.java:31)
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl$MultiTenantConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcServicesImpl.java:302)
...
Caused by: javax.naming.NameNotFoundException: jdbc not bound
at org.jnp.server.NamingServer.getBinding(NamingServer.java:529)
at org.jnp.server.NamingServer.getBinding(NamingServer.java:537)
at org.jnp.server.NamingServer.getObject(NamingServer.java:543)
at org.jnp.server.NamingServer.lookup(NamingServer.java:267)
at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:625)
at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:587)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:154)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:104)
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:105)
at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:132)
...

如果我 在 applicationContext-datasources.xml 中将 lookup-on-startup 设置为 true,那么它工作得非常好,但我知道当我的一个数据源无法访问时它不会工作,即使是另一个租户。

提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

我找到了实现目标的方法,但解决方案并不完美。

当我声明我的类 MultiTenantConnectionProviderImpl 时,在方法 getAnyConnection() 中,我提供了一个来自所有租户共享的默认数据库的连接:

public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable 

    @Override
    public Connection getAnyConnection() throws SQLException 
        return defaultDataSource.getConnection();
    
    ...

我让其他方法确定当前租户。

然后,在 applicationContext-datasources.xml 中,我可以随意声明所有其他数据源:

applicationContext-datasources.xml :

<!-- default data source -->
<jee:jndi-lookup id="defaultDatasource" jndi-name="jdbc/defaultDatasource" resource-ref="true" lookup-on-startup="true" expected-type="javax.sql.DataSource" />

<!-- data source 1 -->
<jee:jndi-lookup id="ds1" jndi-name="jdbc/ds1" resource-ref="true" lookup-on-startup="false" expected-type="javax.sql.DataSource" />

<!-- data source 2 -->
<jee:jndi-lookup id="ds2" jndi-name="jdbc/ds2" resource-ref="true" lookup-on-startup="false" expected-type="javax.sql.DataSource" />

这样可以工作,但它需要一个共享数据库。

【讨论】:

以上是关于在休眠多租户应用程序中启动时不查找数据源的主要内容,如果未能解决你的问题,请参考以下文章

H2 的休眠多租户问题:错误的架构

多租户:使用 Spring Data JPA 管理多个数据源

具有多租户休眠的 Spring-Data JPA

使用 Hibernate 和 Spring 根据用户登录连接到租户数据库

在多租户应用程序中动态设置 Flask-SQLAlchemy 数据库连接

jpa休眠一对多双向[重复]