我目前在Spring-boot项目中集成多租户时遇到问题。
[这里,情况是:我将spring boot项目与一个数据库源链接到Postgresql:已知池中的数据库之一,即“ master”,这是spring-boot项目将连接的默认DB至。池中的其他数据库是未知的,我希望能够在应用程序运行时通过API调用将数据库连接切换到它们。在TablesController中,我创建了一个GET METHOD,在查询中获取参数“ envname”,并尝试切换与envname连接的当前数据库,并返回其表的列表。
我尝试过this tutorial,但是我找不到为什么它不起作用。这是我的代码:
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
# General properties
server.port = 9090
# Hibernate properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.show-sql=true
spring.jpa.hibernate.tenant_identifier_resolver= com.example.myproject.util.CurrentTenantIdentifierResolverImp
# Postgresql properties
spring.datasource.url=jdbc:postgresql://localhost:5432/master
spring.datasource.username=postgres
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.initialization-mode=always
spring.datasource.initialize=true
spring.datasource.continue-on-error=true
public class TenantContext {
final public static String DEFAULT_TENANT = "master";
private static ThreadLocal<String> currentTenant = new ThreadLocal<String>()
{
@Override
protected String initialValue() {
return DEFAULT_TENANT;
}
};
public static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
public static String getCurrentTenant() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
@RestController
public class TablesController {
@Autowired
GetTablesName getTablesNames;
@Autowired
DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ApplicationContext appContext;
@Autowired
public BackXPressConfig backXPressConfig;
@GetMapping("/{envname}/tables")
public ResponseEntity<?> getTablesByEnv(@PathVariable("envname") String envname){
TenantContext.setCurrentTenant(envname);
try {
System.out.println(TenantContext.getCurrentTenant());
Object o = JdbcUtils.extractDatabaseMetaData(dataSource, getTablesNames);
System.out.println(o);
return ResponseEntity.ok(o);
} catch (MetaDataAccessException e) {
System.out.println(e);
return null;
}
}
}
@Component
public class TenantIdentifier implements CurrentTenantIdentifierResolver{
@Override
public String resolveCurrentTenantIdentifier() {
return TenantContext.getCurrentTenant();
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
@Component
public class TenantInterceptor extends HandlerInterceptorAdapter {
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
TenantContext.clear();
}
}
@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<>();
properties.putAll(jpaProperties.getProperties());
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
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.myproject");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
@Service
public class GetTablesName implements DatabaseMetaDataCallback {
public List<String> processMetaData(DatabaseMetaData metaData) throws SQLException {
ResultSet rawDataSet = metaData.getTables(metaData.getUserName(), null, null, new String[]{"TABLE"});
List<String> listTable = new ArrayList<String>();
while (rawDataSet.next()) {
listTable.add(rawDataSet.getString(3));
}
return listTable;
}
}
@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 6246085840652870138L;
@Autowired
private 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 {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
connection.close();
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
}