【发布时间】:2015-08-25 18:21:02
【问题描述】:
我们正在为多个消费者开发 SaaS 解决方案。该解决方案基于 Spring、Wicket 和 Hibernate。我们的 数据库包含来自多个客户的数据。我们决定对数据库进行如下建模:
- 公开
所有客户之间的共享数据,例如用户帐户,因为我们不知道用户属于哪个客户 - customer_1
- customer_2
- ...
要使用此设置,我们使用具有以下 TenantIdentifierResolver 的多租户设置:
public class TenantProviderImpl implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> tenant = new ThreadLocal<>();
public static void setTenant(String tenant){
TenantProviderImpl.tenant.set(tenant);
}
@Override
public String resolveCurrentTenantIdentifier() {
return tenant.get();
}
@Override
public boolean validateExistingCurrentSessions() {
return false;
}
/**
* Initialize a tenant by storing the tenant identifier in both the HTTP session and the ThreadLocal
*
* @param String tenant Tenant identifier to be stored
*/
public static void initTenant(String tenant) {
HttpServletRequest req = ((ServletWebRequest) RequestCycle.get().getRequest()).getContainerRequest();
req.getSession().setAttribute("tenant", tenant);
TenantProviderImpl.setTenant(tenant);
}
}
initTenant 方法由 servlet 过滤器针对每个请求调用。此过滤器在连接之前处理
已打开数据库。
我们还实现了一个AbstractDataSourceBasedMultiTenantConnectionProviderImpl,它被设置为我们的
hibernate.multi_tenant_connection_provider。它在每个请求之前发出SET search_path 查询。这对通过上述 servlet 过滤器的请求很有效。
现在我们的真正问题是:我们的应用程序中有一些入口点没有通过 servlet 过滤器, 例如一些 SOAP 端点。还有一些不通过 servlet 过滤器的定时作业被执行。 这被证明是一个问题。
作业/端点以某种方式接收一个值,该值可用于识别应与哪个客户相关联
作业/端点请求。这个唯一值通常映射到我们的public 数据库模式中。因此,我们需要查询
在我们知道与哪个客户相关联之前的数据库。因此,Spring 初始化了一个完整的 Hibernate 会话。这
session 具有我们的默认租户 ID,并且未映射到特定客户。然而,在我们解决了独特的
我们希望会话更改租户标识符的客户的价值。不过,这似乎不受支持,有
没有 HibernateSession.setTenantIdentifier(String) 这样的东西,而有
SharedSessionContract.getTenantIdentifier().
我们认为我们有以下方法的解决方案:
org.hibernate.SessionFactory sessionFactory = getSessionFactory();
org.hibernate.Session session = null;
try
{
session = getSession();
if (session != null)
{
if(session.isDirty())
{
session.flush();
}
if(!session.getTransaction().wasCommitted())
{
session.getTransaction().commit();
}
session.disconnect();
session.close();
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
}
catch (HibernateException e)
{
// NO-OP, apparently there was no session yet
}
TenantProviderImpl.setTenant(tenant);
session = sessionFactory.openSession();
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
return session;
但是,此方法似乎在 Job/Endpoint 的上下文中不起作用,并导致 HibernateException 例如
“会议结束!”或“交易未成功启动”。
我们有点迷失了,因为我们一直在努力寻找解决方案。我们有什么误解吗? 我们误解了什么?我们如何解决上述问题?
回顾:HibernateSession-s 不是由用户请求创建的,而是由定时作业创建的,或者类似的不通过我们的 servlet
过滤器,因此在 Hibernate 会话开始之前没有关联的租户标识符。他们有独特的价值
我们可以通过查询数据库将其转换为租户标识符。我们如何告诉现有的 Hibernate
会话更改其租户标识符,从而发出新的SET search_path 语句?
【问题讨论】:
-
有点晚了,但这似乎很相关hibernate.atlassian.net/browse/HHH-9766
-
看来你是对的,ticket 指出这实际上是迄今为止不受支持的行为。我们找到了一种解决方法,我将在下面描述...
-
我在这里很晚了,但是 AOP 会帮助你吗?我不确定,但在执行 SOAP 端点(在路径中包含tenantId)和石英调度程序(我认为有一种方法可以将数据传递给调度程序,我们可以在那里传递tenantId)之前可能会有所帮助?
-
我不确定 AOP 是否有帮助,我对 AOP 的可能性不太熟悉。该问题与我们使用 OpenSessionInViewFilter 所做的设计选择有关,其目标是防止在单个请求线程中使用多个会话。这与 Hibernate 不允许在打开的会话中切换租户的事实相结合,这意味着它不容易实现。我们已经构建了另一个没有 OpenSessionInViewFilter 的基于 Hibernate 的应用程序,这根本不是问题。
-
@Ermintar 的这个解决方案对我来说效果很好。 stackoverflow.com/questions/46847059/…
标签: java spring hibernate session servlets