SpringBoot通过MyBatis实现基于Schema的多租户模式?

使用MyBatis来实现基于Schema模式的多租户应用,可以结合之前博客中分享的内容做一些调整,多租户实现,需要基于MyBatis的配置进行一系列的调整配置数据源和多租户。下面我们就来详细介绍一下如何通过MyBatis实现多租户处理。
项目依赖
在项目的POM文件中添加相关的依赖配置,如下所示。
<dependencies>
<!-- Spring Boot MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- HikariCP for connection pool -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- PostgreSQL driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
配置数据库
在配置文件中添加连接数据库配置,如下所示。
spring.datasource.url=jdbc:postgresql://localhost:5432/multitenantdb
spring.datasource.username=your_db_user
spring.datasource.password=your_db_password
spring.datasource.driver-class-name=org.postgresql.Driver
mybatis.type-aliases-package=com.example.demo.domain
mybatis.mapper-locations=classpath:mapper/*.xml
实现多租户功能
创建TenantContext类,与通过JPA技术实现的方式类似,我们需要创建一个TenantContext类来保存当前请求的租户标识符,如下所示。
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenant(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public static String getTenant() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
创建一个自定义的数据源MultiTenantDataSource来管理多租户数据源。这个数据源会根据当前租户设置连接到对应的Schema。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class MultiTenantDataSource extends AbstractRoutingDataSource {
public MultiTenantDataSource(DataSource defaultDataSource, Map<Object, Object> tenantDataSources) {
setDefaultTargetDataSource(defaultDataSource);
setTargetDataSources(tenantDataSources);
afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getTenant();
}
}
配置DataSource和MyBatis
在DataSourceConfig类中配置数据源和MyBatis,如下所示。
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper")
public class DataSourceConfig {
@Autowired
private DataSource defaultDataSource;
@Bean
public MultiTenantDataSource dataSource() {
Map<Object, Object> tenantDataSources = new HashMap<>();
// 示例: 配置多个租户的数据源
tenantDataSources.put("tenant1", createTenantDataSource("schema1"));
tenantDataSources.put("tenant2", createTenantDataSource("schema2"));
return new MultiTenantDataSource(defaultDataSource, tenantDataSources);
}
private DataSource createTenantDataSource(String schema) {
// 这里可以基于当前schema创建一个新的DataSource
// 并且配置连接到这个schema(例如通过连接URL中指定search_path)
return defaultDataSource; // 简化处理,实际应返回基于schema的新DataSource
}
@Bean
public SqlSessionFactory sqlSessionFactory(MultiTenantDataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sessionFactory.getObject();
}
@Bean
public PlatformTransactionManager transactionManager(MultiTenantDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
配置租户信息的解析
可以在Spring的过滤器中设置 TenantContext,在每个请求中解析租户信息,通过请求头或URL参数传递租户ID,如下所示。
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String tenantId = request.getHeader("X-TenantID");
if (tenantId != null) {
TenantContext.setTenant(tenantId);
}
try {
filterChain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
使用MyBatis的Mapper
通过定义Mapper接口和对应的XML文件来操作数据库,在执行SQL语句时,MyBatis会根据当前租户自动选择对应的Schema。
总结
这种方法通过MyBatis和Spring Boot实现了基于Schema的多租户架构。通过在每个请求开始时解析并设置当前租户信息,可以动态切换到不同的Schema,实现数据的隔离和独立管理。