我有一个Spring Boot 2 + Hibernate 5多租户应用程序,它连接到单个PostgreSQL数据库。我已经按照以下指南进行了设置:
只要在到达Controller端点之前在Filter或Interceptor中设置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
强制该方法在单独的事务中运行,因此休眠会话的创建将推迟到调用carRepo.findAll()
方法之前。似乎并非如此,因为@Transactional
什么也不做。
这使我想到两个问题:
@Transactional
似乎没有任何作用。其他相关类别(仅显示相关部分!)
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;
}
}
答案 0 :(得分:2)
在春季,当我们从另一个bean类中调用该方法时,将调用事务。在这种情况下,如果将findAll调用移至服务类并在该方法上添加事务,则行为将与您期望的一样。事务将在您调用服务方法时开始,然后在TenantContext上设置架构值
注意:从Controller中删除@Transactional。由于您正在读取,最好将readonly属性添加到@Transactional中,该方法添加到服务方法'getAllCars()'
@RestController
public class CarController {
@GetMapping("/cars")
public List<Car> getCars(@RequestParam(name = "schema") String schema) {
TenantContext.setCurrentTenant(schema);
return carService.getAllCars();
}
}
@Service
public class CarService{
@Transactional(readOnly=true)
public List<Car> getAllCars() {
return carRepo.findAll();
}
}
答案 1 :(得分:1)
@ tan-mally清楚地说明了我对@Transaction
的问题以及如何解决的问题,而实际问题是由不同的Spring Boot配置默认值spring.jpa.open-in-view=true
将其设置为false
时,我根本不需要@Transaction
注释。连接的检索将推迟到遇到存储库的findAll()
方法之后,在调用TenantContext.setCurrentTenant(schema)
的之后。
显然,spring.jpa.open-in-view=true
总是急切地围绕整个请求创建一个Hibernate会话。
希望这可以帮助下一个遇到此问题的人。仅在启动时弹出有关此默认设置的警告提示我此属性。有关此主题的讨论,请参见this Github issue。