[042][数据模块]Mybatis Plus 数据库级租户:基于多数据源路由的动态隔离实现 [042][数据模块]Mybatis Plus 数据库级租户基于多数据源路由的动态隔离实现本项目代码: https://gitee.com/yunjiao-source/tutorials4j在多租户系统中租户数据隔离是核心设计问题之一。常见的隔离方案包括“共享表 tenant_id 字段”、“共享数据库 独立 schema”以及“独立数据库”。本文聚焦于**数据库级database**隔离策略结合 Mybatis Plus 和 Spring 框架通过动态数据源路由实现租户数据自动切换。我们将基于以下三个核心类进行源码级功能分析MybatisPlusTenantConfiguration—— 自动配置入口根据策略激活不同租户实现。MultipleRoutingDataSource—— 继承自AbstractRoutingDataSource实现租户标识到实际数据源的动态映射。DataSourceRoutingManager—— 路由管理器接口负责根据租户标识查找或创建数据源。通过剖析这些组件的协作机制揭示如何在 Mybatis Plus 体系中无侵入地完成数据库级租户隔离。1. 数据库级租户概述数据库级租户意味着每个租户拥有独立的物理数据库或同一个数据库实例下的不同逻辑库。相比字段过滤或 schema 隔离它具有最强的数据安全性并且天然支持租户级别的资源扩展、备份与迁移。其核心挑战在于如何在不修改业务 SQL 的前提下将每个请求动态路由到该租户对应的数据库连接。Mybatis Plus 本身不直接提供数据库级路由能力但我们可以借助 Spring 的AbstractRoutingDataSource和 Mybatis Plus 的SqlSessionFactoryBeanCustomizer实现透明切换。本文代码中租户策略通过配置项tenant.datasource.strategydatabase激活对应的配置类DatabaseTenantConfiguration会向容器注册一个SqlSessionFactoryBeanCustomizer将 Mybatis Plus 的SqlSessionFactory的数据源替换为一个动态路由数据源MultipleRoutingDataSource。2. 代码结构解析涉及三个主要模块类名职责MybatisPlusTenantConfiguration条件化配置根据tenant.datasource.strategy的值table/database分别启用表级拦截器或数据库级路由定制器。MultipleRoutingDataSource自定义的动态路由数据源从TenantContextHolder获取当前租户标识委托给DataSourceRoutingManager获取对应的真实数据源。DataSourceRoutingManager接口定义按租户名称查找数据源、添加 JDBC 配置、关闭等操作。其实现类代码未给出但可从接口推断负责管理租户与数据源的映射关系及动态创建。此外TenantContextHolder是一个线程局部变量工具用于保存当前请求的租户标识通常在 Web 过滤器中从请求头或 JWT 解析后设置。3. 关键组件分析3.1 MybatisPlusTenantConfiguration策略开关ConditionalOnProperty(prefixPropertiesConsts.PROPERTY_PREFIX_TENANT,namedatasource.strategy,havingValuedatabase)staticclassDatabaseTenantConfiguration{BeanSqlSessionFactoryBeanCustomizerdatabaseSqlSessionFactoryBeanCustomizer(MultipleRoutingDataSourcedataSource){returnfactoryBean-factoryBean.setDataSource(dataSource);}}条件触发只有当tenant.datasource.strategydatabase时该内部配置类才会生效。核心 Bean注册一个SqlSessionFactoryBeanCustomizer它的作用是在 Mybatis Plus 构建SqlSessionFactory之前将factoryBean的数据源设置为MultipleRoutingDataSource。依赖注入MultipleRoutingDataSource需要预先定义为 Spring Bean通常由独立的DataSourceConfiguration创建。对比表级租户havingValuetable那里注册的是MybatisPlusInterceptorCustomizer用于添加租户 SQL 拦截器SimpleTenantLineInterceptorCustomizer。两种策略互斥配置清晰。3.2 MultipleRoutingDataSource动态路由数据源publicclassMultipleRoutingDataSourceextendsAbstractRoutingDataSource{privatefinalDataSourceRoutingManagerdataSourceRoutingManager;OverrideprotectedDataSourcedetermineTargetDataSource(){ObjectlookupKeydetermineCurrentLookupKey();if(lookupKeyinstanceofStringlookupKeyStr){returndataSourceRoutingManager.determineTargetDataSource(lookupKeyStr);}thrownewIllegalStateException(不支持的多数据源键lookupKey);}OverrideprotectedObjectdetermineCurrentLookupKey(){returnTenantContextHolder.get();}}继承 Spring 抽象类AbstractRoutingDataSource是多数据源路由的标准实现。它会在每次获取连接时调用determineCurrentLookupKey()得到路由键再根据该键从targetDataSources映射中获取真实数据源。路由键获取determineCurrentLookupKey()直接返回TenantContextHolder.get()例如租户代码tenant_001。数据源解析委托determineTargetDataSource()没有使用父类维护的targetDataSourcesmap而是完全委托给DataSourceRoutingManager。这样设计的原因是租户数量可能非常多动态增减不可能提前将所有数据源注册到 map 中。路由管理器可以按需创建、缓存和销毁数据源。构造方法接收路由管理器和默认数据源并将默认数据源以默认租户键DefaultConsts.DEFAULT_TENTANT_CODE存入父类的targetDataSources作为后备。3.3 DataSourceRoutingManager路由策略抽象接口定义如下publicinterfaceDataSourceRoutingManager{DataSourcedetermineTargetDataSource(Stringname);DataSourcegetDefaultDataSource();voidaddRoutingJdbcOptions(Stringname,JdbcOptionsjdbcOptions);voidshutdown();}determineTargetDataSource(String name)核心方法根据租户名返回对应的DataSource。实现类通常维护一个MapString, DataSource缓存当 name 不存在时根据预先注册的JdbcOptions动态创建新的 HikariCP/Druid 数据源并放入缓存。addRoutingJdbcOptions用于动态注册新租户的 JDBC 连接信息。例如在租户入驻时调用此接口将租户的数据库 URL、用户名、密码等存储起来供后续动态创建数据源使用。shutdown()在应用关闭时优雅释放所有动态创建的数据源关闭连接池。虽然没有给出实现类但此接口清晰地表达了数据库级租户的核心能力——按需创建、缓存、销毁租户数据源。4. 工作流程一个典型的请求处理流程如下请求进入过滤器/拦截器解析请求头如X-Tenant-ID调用TenantContextHolder.set(tenantId)。Mybatis Plus 执行数据库操作Mybatis 的SqlSession通过SqlSessionFactory获取连接。由于SqlSessionFactory的dataSource已经被替换为MultipleRoutingDataSource因此会调用其getConnection()。动态路由数据源选择MultipleRoutingDataSource.determineCurrentLookupKey()→ 返回TenantContextHolder.get()租户ID。determineTargetDataSource()→ 调用dataSourceRoutingManager.determineTargetDataSource(tenantId)。路由管理器从缓存中查找该租户的数据源如果不存在则根据提前注册的JdbcOptions或从配置中心拉取动态创建并缓存。获取真实数据库连接路由管理器返回真实数据源MultipleRoutingDataSource再从其getConnection()获得连接供 Mybatis 使用。清除租户上下文请求结束后在过滤器 finally 块中调用TenantContextHolder.clear()避免线程池复用时的污染。5. 优缺点分析5.1 优势极强的隔离性租户数据物理分离安全性最高符合金融、医疗等严格合规场景。无 SQL 侵入不需要在每张表中添加tenant_id字段也不需要修改业务查询语句。弹性扩展可为大租户单独配置更高规格的数据库甚至迁移到独立数据库实例。Mybatis Plus 无缝集成通过SqlSessionFactoryBeanCustomizer替换数据源对 Mapper 层完全透明。5.2 挑战与注意事项连接数膨胀每个租户独立数据源意味着每个租户至少占用一个连接池租户数量较大时例如上千租户会耗尽数据库连接数。需配合连接池共享如 HikariCP 的共享连接或使用云原生数据库代理。动态数据源管理复杂度需要自行实现DataSourceRoutingManager的缓存、懒加载、健康检查、关闭等逻辑。跨租户查询困难若需要跨租户聚合统计如管理员查看所有租户数据无法单库 SQL 完成需借助数据仓库或分布式查询引擎。事务边界一个请求只能操作一个租户数据库。如果业务需要同时操作多个租户需使用分布式事务或任务编排。6. 与表级租户的对比维度数据库级租户表级租户字段过滤实现方式动态数据源路由Mybatis Plus 拦截器自动拼接tenant_id ?数据隔离程度物理隔离独立库逻辑隔离共享表租户数量上限受限于数据库实例连接数几乎无限制运维复杂度高备份恢复、迁移每个租户库低统一管理跨租户查询困难需多数据源联合简单修改过滤条件即可适用场景企业级高安全要求、租户数少200SaaS 通用型、租户数成百上千7. 总结本文通过对MybatisPlusTenantConfiguration、MultipleRoutingDataSource和DataSourceRoutingManager三个核心类的功能剖析展示了基于 Mybatis Plus 实现数据库级租户隔离的完整设计思路。该方案的核心价值在于利用 Spring 的抽象路由数据源 Mybatis Plus 的定制扩展点在不侵入业务代码的前提下将每个租户的数据库操作透明地路由到其专属数据库。开发者只需配置tenant.datasource.strategydatabase实现DataSourceRoutingManager接口管理租户数据源的动态创建与缓存在请求入口设置TenantContextHolder。即可获得一套简洁、安全、可扩展的数据库级多租户架构。当然生产落地时还需考虑连接池上限、数据源监控、事务一致性等进阶问题但本文提供的代码骨架已经为这些增强留出了清晰的扩展点。