Bas*_*ord 5 java session spring hibernate servlets
我们正在为多个消费者开发 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);
}
}
Run Code Online (Sandbox Code Playgroud)
该initTenant方法由 servlet 过滤器为每个请求调用。在打开与数据库的连接之前处理此过滤器。
我们还实现了AbstractDataSourceBasedMultiTenantConnectionProviderImpl设置为我们的
hibernate.multi_tenant_connection_provider. 它SET search_path在每个请求之前发出一个查询。这对于通过上述 servlet 过滤器的请求来说就像魅力一样。
现在来解决我们真正的问题:我们的应用程序中有一些入口点没有通过 servlet 过滤器,例如一些 SOAP 端点。还有一些定时作业执行时没有通过 servlet 过滤器。这证明是一个问题。
作业/端点以某种方式接收一个值,该值可用于识别应与作业/端点请求关联的客户。这个唯一值通常映射在我们的public数据库模式中。因此,我们需要在知道关联哪个客户之前查询数据库。因此,Spring 会初始化一个完整的 Hibernate 会话。此会话具有我们的默认租户 ID,未映射到特定客户。但是,在我们将唯一值解析给客户之后,我们希望会话更改租户标识符。虽然这似乎不受支持,但没有 aHibernateSession.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;
Run Code Online (Sandbox Code Playgroud)
然而,这种方法似乎在作业/端点的上下文中不起作用,并导致HibernateException诸如“会话已关闭!” 或“交易未成功启动”。
我们有点迷茫,因为我们一直在努力寻找解决方案。是不是我们误解了什么?我们误解了什么?我们如何解决上述问题?
回顾:HibernateSession-s 不是由用户请求创建的,而是由定时作业或此类创建的,不会通过我们的 servlet 过滤器,因此在 Hibernate 会话开始之前没有关联的租户标识符。它们具有唯一值,我们可以通过查询数据库将其转换为租户标识符。我们如何告诉现有的 Hibernate 会话改变它的租户标识符,从而发出一个新的SET search_path语句?
我们从未找到解决此问题的真正解决方案,但是链接到 Jira 票证的chimmi是其他人请求的这样的功能:https ://hibernate.atlassian.net/browse/HHH-9766
根据此票证,我们想要的行为目前不受支持。不过,我们找到了一种解决方法,因为我们实际想要使用此功能的次数是有限的,因此我们可以使用默认的 java 并发实现在单独的线程中运行这些操作。
通过在单独的线程中运行该操作,将创建一个新会话(因为该会话是线程绑定的)。将租户设置为跨线程共享的变量对我们来说非常重要。为此,我们在 CurrentTenantIdentifierResolver 中有一个静态变量。
为了在单独的线程中运行操作,我们实现了一个Callable. 这些可调用对象被实现为具有作用域的 Spring-bean prototype,因此每次请求时都会创建一个新实例(自动装配)。我们已经实现了自己的抽象实现,Callable它完成了接口call()定义的方法Callable,并且该实现启动了一个新的 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
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4233 次 |
| 最近记录: |