У меня есть мультитенантное приложение Spring Boot 2 + Hibernate 5, подключающееся к одной базе данных PostgreSQL. Я настроил это в соответствии с этими инструкциями:
Это работает нормально, пока я устанавливаю 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 будет отложено до вызова метода carRepo.findAll()
. Похоже, это не так, поскольку @Transactional
ничего не делает.
Это приводит меня к 2 вопросам:
- Как я могу отложить создание сеанса Hibernate во время запроса до тех пор, пока мне не удастся установить правильный арендатор на основе некоторой логики, недоступной в фильтре / перехватчике?
@Transactional
похоже ничего не делает.
- Как я могу общаться с разными схемами в одном запросе или блоке кода? Представьте, что 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;
}
}