休眠多租户:更改会话中的租户

Bas Dalenoord

我们正在为多个消费者开发SaaS解决方案。该解决方案基于Spring,Wicket和Hibernate。我们的数据库包含来自多个客户的数据。我们已决定对数据库进行如下建模:

  • 所有客户之间的公共
    共享数据,例如用户帐户,因为我们不知道该用户属于哪个客户
  • 客户_1
  • 客户_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);
    }
}

initTenantServlet过滤器针对每个请求调用方法。在打开与数据库的连接之前,将处理此过滤器。

我们还实现了AbstractDataSourceBasedMultiTenantConnectionProviderImpl设置为的hibernate.multi_tenant_connection_providerSET 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声明?

Bas Dalenoord

我们尚未找到解决此问题的真正解决方案,但其他人要求将与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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

多租户与单租户?

来自分类Dev

注入服务的休眠多租户实现问题

来自分类Dev

注入服务的休眠多租户实现问题

来自分类Dev

NPE的休眠多租户测试失败

来自分类Dev

基于更改日志的租户架构更新中的Liqubase多租户问题

来自分类Dev

多租户应用中的授权

来自分类Dev

Google Dialogflow中的多租户

来自分类Dev

多租户系统中租户控制队列并发

来自分类Dev

Oracle多租户-从PL / SQL更改容器

来自分类Dev

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

来自分类Dev

在运行时休眠多租户创建架构

来自分类Dev

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

来自分类Dev

无法初始化代理-休眠-Grails-多租户

来自分类Dev

多租户子域在Laravel 5中

来自分类Dev

Laravel口才ORM中的多租户关系

来自分类Dev

Laravel Tinker在多租户环境中

来自分类Dev

回送4中基于架构的多租户

来自分类Dev

多租户解决方案中的PostSharp

来自分类Dev

多租户应用程序中的IdentityRole

来自分类Dev

Microsoft Graph 中的多租户问题

来自分类Dev

Tomcat上的多租户

来自分类Dev

Wordpress的多租户插件

来自分类Dev

Hstore和多租户

来自分类Dev

服务结构多租户

来自分类Dev

Linux DNS多租户

来自分类Dev

Django多租户

来自分类Dev

Stormpath多租户快递

来自分类Dev

在多租户架构中将每个租户的异常记录在单独的文件中

来自分类Dev

在多租户Rails 4中使用Appartment gem时如何获取当前租户