Java 跨多个租户查询表(表名相同)

Java 跨多个租户查询表(表名相同),java,hibernate,spring-data-jpa,multi-tenant,Java,Hibernate,Spring Data Jpa,Multi Tenant,我有一个系统,其中有未知数量的租户(同一数据库服务器上的不同数据库实例)。我有一个工作代码,其中用户登录并选择了正确的租户,我可以读取该租户的配置表 我希望应用程序在启动时遍历所有租户,读取配置并执行操作。在迁移到SpringDataJPA(由hibernate支持)之前,这很容易,因为我分别连接到每个数据库实例 我认为我不能使用Spring的@Transactional,因为它只建立一个连接 我希望对同一个bean使用同一个存储库接口,因为当我一次只需要访问一个租户时,这是有效的 我确实有一个

我有一个系统,其中有未知数量的租户(同一数据库服务器上的不同数据库实例)。我有一个工作代码,其中用户登录并选择了正确的租户,我可以读取该租户的配置表

我希望应用程序在启动时遍历所有租户,读取配置并执行操作。在迁移到SpringDataJPA(由hibernate支持)之前,这很容易,因为我分别连接到每个数据库实例

我认为我不能使用Spring的
@Transactional
,因为它只建立一个连接

我希望对同一个bean使用同一个存储库接口,因为当我一次只需要访问一个租户时,这是有效的


我确实有一个
类MultiTenantConnectionProviderImpl Extendes AbstractDataSourceBasedMultiTenantConnectionProviderImpl
,它将为给定的租户提供一个数据源,但我不确定如何在
@Service
类的方法中使用它?

我认为我接近一个解决方案,但我并不完全满意它。我希望能有一个更好的答案

EDITED:结果表明这并不完全有效,因为Spring或Hibernate似乎只调用当前租户标识符解析器一次,而不是每次调用@Transactional方法

它涉及更改
CurrentTenantIdentifierResolver
实现,以便不仅查看当前用户(如果已设置)以获取其当前租户id(由实现者确定如何设置该id)…还需要查看线程局部变量以查看是否已设置覆盖

使用这种方法,我可以临时设置tenantID…在指定多租户事务管理器的情况下调用服务方法,然后获取数据

我的测试服务:

@Service
public class TestService
{
    @Transactional(transactionManager = "sharedTxMgr")
    public void doSomeGets()
    {
        List<String> tenants = getListSomehow();
        try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride())
        {
            for(String tenant : tenants)
            {
                tempOverride.setCurrentTenant(tenant);
                doTenantSpecificWork();
            }
        }
        catch (Exception e)
        {
            logger.error(e);
        }
    }

    @Transactional(transactionManager = "tenantSpecificTxMgr")
    public void doTenantSpecificWork()
    {
        //do some work here, which only applies to the tenant
    }
}

我不确定我是否应该删除我以前的答案,编辑它或者做些什么。所以,如果国防部能让我知道正确的程序,我很乐意遵守

事实证明我是对的,
@Transactional
的使用不会起作用。最后,我使用了and
AbstractRoutingDataSource
的自定义实现来替换我的
MultiTenantConnectionProviderImpl
CurrentTenantResolverImpl
。我使用这个新数据源,而不是设置
hibernate.multi-tenance
hibernate.multi-tenant\u connection\u provider
hibernate.tenant\u identifier\u resolver

我的临时重写类如下所示:

public class MultitenancyTemporaryOverride implements AutoCloseable
{    
    static final ThreadLocal<String> tenantOverride = new NamedThreadLocal<>("temporaryTenantOverride");

    public void setCurrentTenant(String tenantId)
    {
        tenantOverride.set(tenantId);
    }

    public String getCurrentTenant()
    {
        return tenantOverride.get();
    }

    @Override
    public void close() throws Exception
    {
        tenantOverride.remove();
    }
}
@Component
public class TenantRoutingDataSource extends AbstractDataSource implements InitializingBean
{

    @Override
    public Connection getConnection() throws SQLException
    {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException
    {
        return determineTargetDataSource().getConnection(username, password);
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {
    }

    protected String determineCurrentLookupKey()
    {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String database = "shared";
        if (authentication != null && authentication.getPrincipal() instanceof MyUser)
        {
            MyUser user = (MyUser) authentication.getPrincipal();
            database = user.getTenantId();
        }
        String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get();
        if (temporaryOverride != null)
        {
            database = temporaryOverride;
        }
        return database;
    }

    protected DataSource determineTargetDataSource()
    {
        return selectDataSource(determineCurrentLookupKey());
    }

    public DataSource selectDataSource(String tenantIdentifier)
    {
        //I use C3P0 for my connection pool
        PooledDataSource pds = C3P0Registry.pooledDataSourceByName(tenantIdentifier);
        if (pds == null)
            pds = getComboPooledDataSource(tenantIdentifier);
        return pds;
    }

    private ComboPooledDataSource getComboPooledDataSource(String tenantIdentifier)
    {
        ComboPooledDataSource cpds = new ComboPooledDataSource(tenantIdentifier);
        cpds.setJdbcUrl("A JDBC STRING HERE");
        cpds.setUser("MyDbUsername");
        cpds.setPassword("MyDbPassword");
        cpds.setInitialPoolSize(10);
        cpds.setMaxConnectionAge(10000);
        try
        {
            cpds.setDriverClass("com.informix.jdbc.IfxDriver");
        }
        catch (PropertyVetoException e)
        {
            throw new RuntimeException("Weird error when setting the driver class", e);
        }
        return cpds;
    }
}
然后,在创建实体管理器工厂bean时,我只需将自定义数据源提供给实体管理器工厂bean

@Service
public class TestService
{
    public void doSomeGets()
    {
        List<String> tenants = getListSomehow();
        try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride())
        {
            for(String tenant : tenants)
            {
                tempOverride.setCurrentTenant(tenant);
                //do some work here, which only applies to the tenant
            }
        }
        catch (Exception e)
        {
            logger.error(e);
        }
    }
}
@服务
公共类测试服务
{
公共无效doSomeGets()
{
列出租户=GetListAngeles();
try(MultitenancyTemporaryOverride tempOverride=新建MultitenancyTemporaryOverride())
{
for(字符串租户:租户)
{
tempOverride.setCurrentTenant(租户);
//在这里做一些工作,这只适用于租户
}
}
捕获(例外e)
{
错误(e);
}
}
}

当您说“读取配置”时,是指每个租户数据库中的一个表吗?你可能想看看这个博客,它可能会给你一些想法。如果您有一个数据源列表,我认为在应用程序启动时循环这些数据源并不困难。使用“配置表”作为示例可能是一个错误的选择,因为它可能会混淆问题。事实上,由于各种原因,我有一些东西需要所有租户阅读。您提供的博文建议提前了解数据源,我不知道。多租户是指在数据库服务器上有一个名为
users
的表,该表出现在多个数据库中,如
dropbox\u db
google\u drive\u db
sky\u drive\u db
等?
@Component
public class TenantRoutingDataSource extends AbstractDataSource implements InitializingBean
{

    @Override
    public Connection getConnection() throws SQLException
    {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException
    {
        return determineTargetDataSource().getConnection(username, password);
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {
    }

    protected String determineCurrentLookupKey()
    {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String database = "shared";
        if (authentication != null && authentication.getPrincipal() instanceof MyUser)
        {
            MyUser user = (MyUser) authentication.getPrincipal();
            database = user.getTenantId();
        }
        String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get();
        if (temporaryOverride != null)
        {
            database = temporaryOverride;
        }
        return database;
    }

    protected DataSource determineTargetDataSource()
    {
        return selectDataSource(determineCurrentLookupKey());
    }

    public DataSource selectDataSource(String tenantIdentifier)
    {
        //I use C3P0 for my connection pool
        PooledDataSource pds = C3P0Registry.pooledDataSourceByName(tenantIdentifier);
        if (pds == null)
            pds = getComboPooledDataSource(tenantIdentifier);
        return pds;
    }

    private ComboPooledDataSource getComboPooledDataSource(String tenantIdentifier)
    {
        ComboPooledDataSource cpds = new ComboPooledDataSource(tenantIdentifier);
        cpds.setJdbcUrl("A JDBC STRING HERE");
        cpds.setUser("MyDbUsername");
        cpds.setPassword("MyDbPassword");
        cpds.setInitialPoolSize(10);
        cpds.setMaxConnectionAge(10000);
        try
        {
            cpds.setDriverClass("com.informix.jdbc.IfxDriver");
        }
        catch (PropertyVetoException e)
        {
            throw new RuntimeException("Weird error when setting the driver class", e);
        }
        return cpds;
    }
}
@Service
public class TestService
{
    public void doSomeGets()
    {
        List<String> tenants = getListSomehow();
        try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride())
        {
            for(String tenant : tenants)
            {
                tempOverride.setCurrentTenant(tenant);
                //do some work here, which only applies to the tenant
            }
        }
        catch (Exception e)
        {
            logger.error(e);
        }
    }
}