在 Druid 中配置和使用多租户数据库主要是指在一个应用中支持多个不同的数据库实例或数据库租户。多租户数据库配置通常涉及为不同的租户配置不同的数据源,并确保应用程序可以根据需要选择正确的数据源。以下是如何在 Druid 中配置和使用多租户数据库的方法:
1. 配置多个数据源
首先,你需要为每个租户配置一个数据源。每个数据源代表一个不同的数据库实例或租户。
<!-- 主数据源 -->
<bean id="primaryDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init">
<property name="url" value="jdbc:mysql://primary-db-host:3306/primary_db"/>
<property name="username" value="primary_user"/>
<property name="password" value="primary_pwd"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!-- 租户A的数据源 -->
<bean id="tenantADatasource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init">
<property name="url" value="jdbc:mysql://tenant-a-db-host:3306/tenant_a_db"/>
<property name="username" value="tenant_a_user"/>
<property name="password" value="tenant_a_pwd"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!-- 租户B的数据源 -->
<bean id="tenantBDatasource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init">
<property name="url" value="jdbc:mysql://tenant-b-db-host:3306/tenant_b_db"/>
<property name="username" value="tenant_b_user"/>
<property name="password" value="tenant_b_pwd"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
2. 选择数据源
为了根据租户动态选择合适的数据源,你需要在应用程序中实现某种逻辑来确定当前请求对应的租户,并据此选择正确的数据源。
方法一:使用 AOP(面向切面编程)
可以使用 Spring AOP 来在方法调用前动态选择数据源。例如:
@Aspect
@Component
public class DataSourceAspect {
private final Map<String, DataSource> dataSourceMap = new HashMap<>();
public DataSourceAspect(PrimaryDataSource primaryDataSource,
TenantADatasource tenantADatasource,
TenantBDatasource tenantBDatasource) {
dataSourceMap.put("primary", primaryDataSource);
dataSourceMap.put("tenantA", tenantADatasource);
dataSourceMap.put("tenantB", tenantBDatasource);
}
@Around("@annotation(com.example.annotation.DataSource)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
DataSource dataSourceAnnotation = getDataSourceAnnotation(joinPoint);
DataSource dataSource = dataSourceMap.get(dataSourceAnnotation.value());
setDataSource(dataSource);
try {
return joinPoint.proceed();
} finally {
clearDataSource();
}
}
private DataSource getDataSourceAnnotation(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(DataSource.class);
}
private void setDataSource(DataSource dataSource) {
// 设置当前线程的数据源
}
private void clearDataSource() {
// 清除当前线程的数据源
}
}
在这个例子中,@DataSource
是一个自定义注解,用于指定要使用哪个数据源。
方法二:基于上下文信息
另一种方法是在请求的上下文中保存当前的租户信息,然后在需要的时候根据这个信息选择数据源。
public class TenantContextHolder {
private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();
public static void setTenantId(String tenantId) {
contextHolder.set(tenantId);
}
public static String getTenantId() {
return contextHolder.get();
}
public static void clearTenantId() {
contextHolder.remove();
}
}
// 在过滤器或拦截器中设置租户ID
public class TenantContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String tenantId = httpServletRequest.getHeader("X-Tenant-ID");
TenantContextHolder.setTenantId(tenantId);
try {
chain.doFilter(request, response);
} finally {
TenantContextHolder.clearTenantId();
}
}
}
在 Service 层,根据上下文中的租户 ID 选择数据源:
public class TenantService {
private final Map<String, DataSource> dataSourceMap;
public TenantService(PrimaryDataSource primaryDataSource,
TenantADatasource tenantADatasource,
TenantBDatasource tenantBDatasource) {
dataSourceMap = new HashMap<>();
dataSourceMap.put("primary", primaryDataSource);
dataSourceMap.put("tenantA", tenantADatasource);
dataSourceMap.put("tenantB", tenantBDatasource);
}
public void performOperation() {
String tenantId = TenantContextHolder.getTenantId();
DataSource dataSource = dataSourceMap.get(tenantId);
// 使用 dataSource 执行数据库操作
}
}
通过上述配置,你可以实现多租户数据库的动态选择,确保每个租户都能访问到正确的数据库实例。这不仅提高了系统的灵活性,也为多租户应用程序提供了更好的支持。请注意,具体的实现细节可能根据你的具体需求和应用场景有所不同。