我们正在为多个消费者开发SaaS解决方案。该解决方案基于Spring,Wicket和Hibernate。我们的数据库包含来自多个客户的数据。我们已决定对数据库进行如下建模:
要使用此设置,我们使用带有以下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会话。该会话具有我们的默认租户ID,并且未映射到特定客户。但是,在为客户解决了唯一值之后,我们希望会话更改租户标识符。尽管似乎不支持此功能,但不HibernateSession.setTenantIdentifier(String)
存在a,而存在a 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
声明?
我们尚未找到解决此问题的真正解决方案,但其他人要求将与Jira机票链接的chimmi要求使用此功能:https : //hibernate.atlassian.net/browse/HHH-9766
根据此票证,当前所需的行为目前不受支持。但是,我们已经找到了一种解决方法,因为我们实际要使用此功能的次数受到限制,因此使用默认的Java并发实现在单独的线程中运行这些操作是可行的。
通过在单独的线程中运行该操作,可以创建一个新会话(因为该会话是线程绑定的)。对于我们来说,将租户设置为跨线程共享的变量非常重要。为此,我们在CurrentTenantIdentifierResolver中有一个静态变量。
为了在单独的线程中运行操作,我们实现Callable
。这些可调用对象通过具有范围的Spring-beans实现,prototype
因此每次请求(自动装配)都会创建一个新实例。我们已经实现了自己的抽象实现,Callable
它最终确定call()
了Callable
接口定义的-method ,并且该实现开始了一个新的HibernateSession。代码看起来像这样:
public abstract class OurCallable<TYPE> implements Callable<TYPE> {
private final String tenantId;
@Autowired
private SessionFactory sessionFactory;
// More fields here
public OurCallable(String tenantId) {
this.tenantId = tenantId;
}
@Override
public final TYPE call() throws Exception {
TenantProvider.setTenant(tenantId);
startSession();
try {
return callInternal();
} finally {
stopSession();
}
}
protected abstract TYPE callInternal();
private void startSession(){
// Implementation skipped for clarity
}
private void stopSession(){
// Implementation skipped for clarity
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句