【问题标题】:Change Multitenancy sessions manually手动更改多租户会话
【发布时间】:2018-04-01 12:06:49
【问题描述】:

我需要创建一个多租户应用程序,该应用程序能够在我的 java 代码中的模式之间切换(不是基于用户请求)。

我读过文章: https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/ http://www.greggbolinger.com/tenant-per-schema-with-spring-boot/ 当架构在 Rest-request 中传递时,解决方案工作正常。

但是我需要实现以下逻辑:

public void compare(String originalSchema, String secondSchema){
    TenantContext.setCurrentTenant(originalSchema);
    List<MyObject> originalData = myRepository.findData();

    TenantContext.setCurrentTenant(secondSchema);
    List<MyObject> migratedData = myRepository.findData();
}

关键是,当我手动设置 TenenantContext 时,连接并没有切换。 MultiTenantConnectionProviderImpl.getConnection 仅在第一次调用我的存储库时被调用。

 @Component
 public class MultiTenantConnectionProviderImpl implements  MultiTenantConnectionProvider {

     @Override
     public Connection getConnection(String tenantIdentifier) throws SQLException {
          final Connection connection = getAnyConnection();
          try {
               connection.createStatement().execute( "ALTER SESSION SET CURRENT_SCHEMA = " + tenantIdentifier );
          }
          catch ( SQLException e ) {
              throw new HibernateException(
      "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",e);
          }
          return connection;
    }
 }

是否可以强制切换会话?

【问题讨论】:

  • 是的,这是可能的,但您需要在事务范围之外切换会话。
  • @Andriy Slobodyanyk,我不手动创建交易。我有一项服务,它没有事务注释并调用存储库。交易债券从何而来?

标签: spring hibernate spring-boot spring-data-jpa


【解决方案1】:

找到一个硬编码的解决方案。

@Service
public class DatabaseSessionManager {

    @PersistenceUnit
    private EntityManagerFactory entityManagerFactory;

    public void bindSession() {
        if (!TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
            EntityManager entityManager = entityManagerFactory.createEntityManager();
            TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
        }
    }

    public void unbindSession() {
        EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
            .unbindResource(entityManagerFactory);
        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
    }
}

在新的tenantContext中加载数据的每个块都应该执行以下操作:

    databaseSessionManager.unbindSession();
    TenantContext.setCurrentTenant(schema);
    databaseSessionManager.bindSession();
    //execute selects

【讨论】:

  • 效果很好!注意 - 如果您将 spring.jpa.open-in-view 设置为 false,如果您不先检查资源是否存在,unbindSession 将失败并出现 NPE。
【解决方案2】:

嗯,你需要它

public interface Service {
    List<MyObject> myObjects();
}

@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class ServiceImpl implements Service {
     @Autowired
     private MyRepository myRepository;

     @Override
     public List<MyObject> myObjects() {
         return myRepository.findData();
     }
}

@Service
public class AnotherService() {
    @Autowired
    private Service service;

    public void compare(String originalSchema, String secondSchema){
        TenantContext.setCurrentTenant(originalSchema);
        List<MyObject> originalData = service.myObjects();

        TenantContext.setCurrentTenant(secondSchema);
        List<MyObject> migratedData = service.myObjects();
    }
}

【讨论】:

  • 不幸的是,它就像自动装配存储库一样工作。连接没有切换
  • @Ermintar,您确定之前没有使用任何 sql 操作启动事务吗?为了解决这个问题,我建议在新事务中查找数据,我已经更新了我的答案。
  • @Andriy Slobodyanyk,需要新的没有支持。而且我的服务周围没有事务上下文 - 这是我直接调用的唯一方法。
  • @Ermintar,即使您显然没有启动事务,它也会在您选择数据并且第二次调用使用它并且它是连接时隐式创建。
  • @Andriy Slobodyanyk,我研究了 Spring 源代码。其实持有tenantId的不是thransaction,而是Session。创建 SessionImpl 并将租户从 entitymanager 传递给它。 Tenenat 在会话中不会改变。 SessionImpl 每个休息请求创建一次,所有事务都在会话内工作
猜你喜欢
  • 2015-08-25
  • 1970-01-01
  • 2021-02-24
  • 2014-02-15
  • 2011-10-21
  • 2022-07-26
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多