我正在使用具有多个架构的单个数据库来构建Multitenant saas应用程序;每个客户端一个模式。我正在使用Spring Boot 2.1.5,Hibernate 5.3.10和兼容的spring数据jpa和postgres 11.2。
我关注了此博客文章https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation。
尝试调试代码,以下是我的发现:
*对于数据源配置中提供的默认架构,hibernate会正确验证架构。它在默认架构中创建丢失的表或实体或创建新的表/实体。
*租户标识符已正确解析,休眠使用该租户建立会话。
我已将代码上传到以下仓库中:
https://github.com/naveentulsi/multitenant-lithium
我在这里添加了一些重要的课程。
@Component
@Log4j2
public class MultiTenantConnectionProviderImpl implements
MultiTenantConnectionProvider {
@Autowired
DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
if (!StringUtils.isEmpty(tenantIdentifier)) {
String setTenantQuery = String.format(AppConstants.SCHEMA_CHANGE_QUERY, tenantIdentifier);
connection.createStatement().execute(setTenantQuery);
final ResultSet resultSet = connection.createStatement().executeQuery("select current_schema()");
if(resultSet != null){
final String string = resultSet.getString(1);
log.info("Current Schema" + string);
}
System.out.println("Statement execution");
} else {
connection.createStatement().execute(String.format(AppConstants.SCHEMA_CHANGE_QUERY, AppConstants.DEFAULT_SCHEMA));
}
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
String Query = String.format(AppConstants.DEFAULT_SCHEMA, tenantIdentifier);
connection.createStatement().executeQuery(Query);
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
connection.close();
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
@Configuration
@EnableJpaRepositories
public class ApplicationConfiguration implements WebMvcConfigurer {
@Autowired
JpaProperties jpaProperties;
@Autowired
TenantInterceptor tenantInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor);
}
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create().username(AppConstants.USERNAME).password(AppConstants.PASS)
.url(AppConstants.URL)
.driverClassName("org.postgresql.Driver").build();
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.ddl-auto", "update");
properties.put("hibernate.jdbc.lob.non_contextual_creation", "true");
properties.put("show-sql", "true");
properties.put("hikari.maximum-pool-size", "3");
properties.put("hibernate.default_schema", "master");
properties.put("maximum-pool-size", "2");
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).setMaximumPoolSize(3);
}
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
properties.put(Environment.FORMAT_SQL, true);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.saas");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
@Component
public class TenantResolver implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> TENANT_IDENTIFIER = new ThreadLocal<>();
public static void setTenantIdentifier(String tenantIdentifier) {
TENANT_IDENTIFIER.set(tenantIdentifier);
}
public static void reset() {
TENANT_IDENTIFIER.remove();
}
@Override
public String resolveCurrentTenantIdentifier() {
String currentTenant = TENANT_IDENTIFIER.get() != null ? TENANT_IDENTIFIER.get() : AppConstants.DEFAULT_SCHEMA;
return currentTenant;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
由TenantResolver成功注入TenantId后,entityManager应该能够将实体存储到数据库中相应的租户架构中。也就是说,如果我们创建实体的对象并将其持久保存在db中,则应成功将其保存在db中。但就我而言,实体不会保存到默认模式以外的任何模式中。
更新1:我能够使用mysql 8.0.12进行多租户架构切换。仍然无法使用postgres做到这一点。
答案 0 :(得分:0)
您应该使用AbstractRoutingDataSource来实现这一目标,它可以完成所有幕后工作,在线上有很多示例,您可以在https://www.baeldung.com/spring-abstract-routing-data-source上找到一个示例
答案 1 :(得分:0)
在您的类“ ApplicationConfiguration.java”中;
您必须删除此“ properties.put(” hibernate.default_schema“,” master“);”,为什么因为每次更改架构时它都可以更改,但是当它到达此行时又一次设置默认架构
希望你能得到答案
谢谢大家
保重!