首页 > 解决方案 > Spring Boot + Hibernate 多租户:@Transactional 不起作用

问题描述

我有一个连接到单个 PostgreSQL 数据库的 Spring Boot 2 + Hibernate 5 多租户应用程序。我已经根据这些指南进行了设置:

只要我在到达控制器端点之前在过滤器或拦截器中设置了tenantId,这就可以正常工作。

但是,我需要在控制器内设置租户,如下:

@RestController
public class CarController {
    @GetMapping("/cars")
    @Transactional
    public List<Car> getCars(@RequestParam(name = "schema") String schema) {
        TenantContext.setCurrentTenant(schema);
        return carRepo.findAll();
    }
}

但是此时已经检索到一个连接(对于公共模式)并且设置TenantContext没有效果。

我认为@Transactional应该强制该方法在单独的事务中运行,因此 Hibernate Session 的创建将被推迟到carRepo.findAll()调用该方法之前。情况似乎并非如此,因为@Transactional什么都不做。

这让我想到了两个问题:

  1. 如何在请求期间推迟 Hibernate 会话的创建,直到我设法根据过滤器/拦截器中不可用的某些逻辑设置正确的租户?@Transactional似乎什么也没做。
  2. 如何在同一个请求或代码块中与不同的模式对话?想象一下 1 个存储库仅在公共模式中可用,而 1 个在租户模式中。

其他相关类(仅显示相关部分!)

MultiTenantConnectionProviderImpl.java:

@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        connection.setSchema(tenantIdentifier);
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        connection.setSchema(null);
        releaseAnyConnection(connection);
    }
}

TenantIdentifierResolver.java

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenantId = TenantContext.getCurrentTenant();
        return (tenantId != null) ? tenantId : "public";
    }
    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

HibernateConfig.java:

@Configuration
public class HibernateConfig {
    @Autowired
    private JpaProperties jpaProperties;

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
                                                                       MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
                                                                       CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
        Map<String, Object> properties = new HashMap<>(jpaProperties.getProperties());
        properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
        properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
        properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example");
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setJpaPropertyMap(properties);
        return em;
    }
}

标签: springhibernatespring-bootjpa

解决方案


虽然@tan-mally@Transaction清楚地解释了我的问题以及如何解决它,但实际问题是由不同的 Spring Boot 配置默认值引起的:spring.jpa.open-in-view=true

将其设置为 时false,我根本不需要@Transaction注释。findAll()调用. _ _TenantContext.setCurrentTenant(schema)

显然spring.jpa.open-in-view=true总是急切地围绕整个请求创建一个 Hibernate 会话。

希望这有助于下一个人遇到这个问题。我只是通过在启动期间弹出有关此默认设置的警告来提示此属性。有关此主题的讨论,请参阅此 Github 问题


推荐阅读