☰
Spring Boot + MyBatis-Plus多数据源整合:Druid监控、SQL过滤与加密实践
2026/9/26 5:05:13 网站建设 项目流程

接手过一个需要拆库的项目,数据库从一套拆成了三套,主库管交易、从库扛查询、还有一个老报表库单独跑定时任务。MyBatis-Plus 本身挺好用,但多数据源切换、Druid 连接池、SQL 过滤和监控这几件事一股脑涌过来的时候,还是需要认真理一理。这篇就把我实际踩过坑、验证过可行的一套组合方案写透:用 dynamic-datasource 做多数据源切换,用 Druid 做连接池和 SQL 过滤,把监控和密码加密也一并配好,同时覆盖 MyBatis-Plus XML 与 Mapper 同目录打包、JPA 与 MyBatis-Plus 选型这类经常被问到的延伸问题。这篇文章适合 Spring Boot 项目里已经遇到过“数据源切不过去”“Druid 监控打不开”“SQL 被墙了不知道怎么回事”的开发者,也适合正准备从单数据源升级到多数据源的团队。

1. 为什么第一反应是 dynamic-datasource 而不是手写路由

1.1 多数据源项目的真实画像

先说清楚“多数据源”到底解决什么场景。最常见的无非三种:读写分离,主库负责写、从库负责读,减少主库压力;业务分库,比如订单库、用户库、日志库各占一台实例,一个服务要同时操作多个库;还有报表或数仓库,跟业务库隔离,避免大查询把在线业务拖垮。这三种场景背后都有一个共同诉求:同一个服务进程里,根据业务代码的调用位置决定这次数据库操作走哪个库,而且切换不能影响到事务一致性和连接复用。

很多团队一开始会想:多数据源不就是多配置几个 DataSource 吗?Spring 里本来就支持为每个数据源配置一套 SqlSessionFactory 或 JpaTransactionManager。可真到了代码里就会发现,如果每换一个库就要注入一套 SqlSessionTemplate,业务代码里到处都是 factoryA、factoryB 的引用,维护成本会直线上升。更麻烦的是,一旦涉及嵌套事务、AOP 切面和连接池复用,纯手工路由很容易在细节上翻车。

1.2 几种实现路径的对比

我简单梳理过三条常见路线,各有各的适用面:

方案核心思路优点缺点
手写 AbstractRoutingDataSource + 自定义注解用 Spring 提供的动态路由 DataSource,在 DAO 层根据上下文切换 key不引入额外依赖,代码透明路由状态容易串,事务和线程池场景下要自己管
ShardingSphere 数据分片把多库抽象成分片规则,按分片键路由适合分库分表规模较大的场景,能力强配置复杂度高,对已有 SQL 有约束
dynamic-datasource-spring-boot-starter基于 AOP + 上下文持有者切换数据源,注解 @DS 声明式路由语义清晰、与 MyBatis-Plus 配合成熟、事务支持完善需要接受框架约定的规则

手写路由最头疼的点是在高并发下如何保证“这次请求的路由状态”不泄漏到下一个请求。Spring 的 AbstractRoutingDataSource 本身只是提供了 determineCurrentLookupKey 这个口子,关键在于你的上下文怎么存、怎么清理。很多人会直接用 ThreadLocal,但用了线程池之后,如果 Web 请求线程处理完没有被正确清理,下一次复用同一个线程就会跑到上一个请求指定的数据源上,这种脏数据问题特别难排查。dynamic-datasource 的做法是把路由状态放到一个被包装过的上下文中,并且会在每次执行后清理,这个细节比大多数团队自己写的版本要稳。

1.3 与 MyBatis-Plus 的配合点

MyBatis-Plus 本身并不负责数据源选择,它只是把 MyBatis 的 Mapper 接口、SqlSession 和 SQL 执行过程包装得更“业务化”。数据源切换发生在 MyBatis 获取连接之前,所以 dynamic-datasource 的 AOP 切面会先把当前数据源 key 放进上下文,然后再进入 MyBatis 执行链路,两者没有冲突。

这也意味着 MyBatis-Plus 的分页插件、乐观锁插件、字段自动填充插件都能照常工作,因为它们是在 SqlSession 层做拦截,不需要关心连接来自哪个 DataSource。真正要注意的只是事务边界的顺序问题,后面第 5 章我会重点展开。选型时优先考虑 dynamic-datasource,而不是自己写路由,本质上就是想把“切换数据源”这个横切关注点交给一个经过验证的框架来处理,让业务代码保持干净。

2. 一步步把多数据源和 Druid 接进来

2.1 依赖引入与版本对应

先把 Maven 依赖放出来。我这里用的是 Spring Boot 2.7 组合,MyBatis-Plus 3.5.x、dynamic-datasource 3.5.2、Druid 1.2.20,这是目前生产环境里验证比较充分的版本组合。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

注意一点:dynamic-datasource 在 3.5.2 版本往上已经对 Spring Boot 3 有单独的 starter,groupId 一样,artifactId 会多一个标识,例如 dynamic-datasource-spring-boot3-starter。如果你的项目已经升级到 Spring Boot 3.2,不要拿普通版硬刚,否则 javax 和 jakarta 的包名冲突会让你在启动阶段就翻车。

2.2 多数据源 yml 编排

依赖装好之后,核心配置集中在 application.yml。我的习惯是先用 spring.datasource.dynamic 这个根节点把多数据源关系维护清楚,再在具体数据源里把 Druid 的连接池参数带上。

spring: datasource: type: com.alibaba.druid.pool.DruidDataSource dynamic: primary: master strict: false datasource: master: url: jdbc:mysql://10.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://10.0.0.2:3306/order_db_read?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: read_user password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver report: url: jdbc:mysql://10.0.0.3:3306/report_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: report_user password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20

关键参数不用全背,但要理解它们各自防的是什么坑。initial-size 是启动时建立的连接数,如果太小,刚启动那会儿流量一大就会频繁建连。test-while-idle 为 true 时,连接在空闲回收线程扫描时会被 validation-query 验证,避免把已经被 MySQL 服务端掐断的连接继续分给业务。max-wait 设置为 60000 毫秒,表示拿连接超过 60 秒就报错而不是无限等,这是防止连接池被耗尽后所有线程全部挂死的关键开关。

2.3 让每个数据源真正走 Druid 连接池

有些人配置完上面的 yml 会发现 Druid 的监控页面 0 数据,或者连接池参数根本没有生效。原因通常是 dynamic-datasource 没有识别到你希望用 Druid 作为连接池。在 spring.datasource 根节点上声明 type 只是给了 Spring Boot 一个提示,但在 dynamic 场景下,更保险的做法是在每个数据源配 driver-class-name 和 type,或者至少在全局 datasource 下声明 type: com.alibaba.druid.pool.DruidDataSource。

我通常还会把 druid 节点放到 dynamic 下面,而不是每个数据源都重复写一遍。这样 master、slave、report 都会继承全局的连接池参数,只有个别库需要单独调参时,再在对应数据源下覆盖。

spring: datasource: dynamic: datasource: master: druid: max-active: 30

master 只有 30 个连接起稍大一点,其他库仍然沿用全局 20,这种优先级的处理方式在多库场景里很实用。

2.4 Spring Boot 3 与参数命名的小提醒

如果你的项目是 Spring Boot 3.2,把 dynamic-datasource 换成 boot3 版本之后,Druid 的 druid-spring-boot-starter 也要关注包名兼容问题,建议直接使用 1.2.18 以上版本。另外在 Spring Boot 2.4 之后,配置文件里的宽松绑定策略有变化,druid 节点下的驼峰参数要严格按规范写,例如 max-pool-prepared-statement-per-connection-size 这种长参数不要漏掉连字符,否则可能静默绑定失败,启动日志里不会有明显报错,但连接池参数就是不对,很难排查。

3. Druid 监控与数据库密码加密

3.1 开监控页面的两种姿势

Druid 的监控页面我一般建议在测试环境开,线上看情况,但最好统一走鉴权。第一种方式是直接使用 druid-spring-boot-starter 提供的自动配置,在 application.yml 里声明 StatViewServlet 的参数:

spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: monitor login-password: your-password web-stat-filter: enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

第二种方式是手写一个 Configuration 类,自己定义 ServletRegistrationBean。我更喜欢这种方式,因为可以把初始化参数、IP 白名单、登录账号都集中到一个类里维护,而且不容易被自动配置的开关绕过去。

@Configuration public class DruidMonitorConfig { @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet() { ServletRegistrationBean<StatViewServlet> registration = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); registration.addInitParameter("loginUsername", "monitor"); registration.addInitParameter("loginPassword", "your-password"); registration.addInitParameter("resetEnable", "false"); registration.addInitParameter("allow", "127.0.0.1,10.0.0.0/24"); registration.addInitParameter("deny", ""); return registration; } @Bean public FilterRegistrationBean<WebStatFilter> webStatFilter() { FilterRegistrationBean<WebStatFilter> registration = new FilterRegistrationBean<>(new WebStatFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); registration.addName("webStatFilter"); return registration; } }

allow 参数是关键,Web 应用如果部署在内网,建议只允许内网网段访问监控页面。resetEnable=false 是防止有人通过 reset-all 按钮把统计数据清掉,生产环境尤其要关掉。deny 参数如果留空字符串表示不拒绝任何来源,所以不要写 deny 项,直接用 allow 白名单收口更稳妥。

3.2 监控页面上能看什么

Druid 的监控数据价值很高,它统计的是连接池和 SQL 两个维度的信息。连接池维度你能看到当前活跃连接数、空闲连接数、等待获取连接的线程数、逻辑连接关闭次数,这些数据能快速判断连接池容量是否够用。

SQL 维度更直接,每次 SQL 的执行次数、总耗时、最大耗时、平均耗时、返回行数、更新行数都会出现在列表里,而且支持按慢 SQL 排序。当某个接口突然变慢,打开 Druid 监控页,按 ExecuteTime 倒序,基本能第一时间锁定是一条大查询导致,还是连接池争抢导致。慢 SQL 统计依赖 StatFilter,这个过滤器在下一章会重点展开。

3.3 用 ConfigTools 给数据库密码加密

这也是若依那套框架里被反复验证过的方式。Druid 提供了 ConfigTools 工具类,借助 RSA 非对称加密,把明文密码变成密文放进配置文件,数据库连接时再用公钥解密。过程和步骤大致如下:

先在本地执行命令,用 druid 的 jar 包生成密钥对:

java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools 你的明文密码

执行完会输出 privateKey、publicKey、password 三个值。privateKey 只用于本地生成密文,生产环境不要出现;publicKey 放到配置文件里作为解密公钥;password 字段里填的是生成的密文。

配置文件这么写:

spring: datasource: dynamic: druid: connect-properties: config.decrypt: true config.decrypt.key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ... datasource: master: password: IcD7R3dRM7Fb3ZqZvY9Ntq7Y4qG6sTfWAbMv...(密文)

这里提醒一个坑:在多数据源场景下,配置键不要写在单数据源的 url 参数里,也不要混在普通的 druid 属性里。connect-properties 才是真正传给 DruidDataSource 的 Properties。如果用了 spring.datasource.druid.connection-properties,但 dynamic 节点下没有把它透传到具体数据源,解密就不会生效,启动时会直接报密码解密失败。

ConfigTools 这种方式的安全性比明文密码好很多,但不代表可以把数据库密码随意写到前端代码或 Git 里。真正要安全,还是建议配合配置中心或环境变量把 publicKey 再单独管理起来。若依有的版本会在 Nacos 里下发一部分参数,配置中心的方式天然能减少部署时手工改配置的成本。

3.4 监控页面隐藏的额外保护

如果你项目里已经引入了 Spring Security,更稳妥的做法是让 Druid 监控页的 URL 不进 Spring Security 的匿名白名单,而是要求登录后才能访问。简单示例就是 SecurityConfig 里直接对 /druid/** 做权限控制,访问时需要拥有 admin 角色。Druid 自带的 loginUsername 和 loginPassword 只是表单登录层,和框架层面的权限拦截可以叠加使用,不要因为是内网就省略。

4. SQL 过滤器:WallFilter、StatFilter、日志 Filter

4.1 Druid 的过滤器链是怎么工作的

Druid 的 filter 机制可以理解成连接池层面的一组“门禁”。业务代码拿到的是 Druid 连接,每次 statement 执行时要穿过过滤器链,经过的过滤器决定了这次 SQL 是被放行、被统计、被改字符集,还是被直接拦截。

常用过滤器有 StatFilter 负责统计和慢 SQL 记录,WallFilter 负责 SQL 防火墙和防注入,Slf4jLogFilter 和 Log4j2Filter 负责打印 SQL 日志,还有 EncodingConvertFilter 可以处理 GBK 和 UTF-8 转换。每个 filter 都是按顺序执行,所以你可以同时开 StatFilter 和 WallFilter,先经过防火墙检查,再被统计模块计数。

有一个容易混淆的点:Druid 的 SQL 过滤器和 MyBatis 的 Interceptor 不是一个层面的东西。MyBatis-Plus 的分页插件、乐观锁插件是作用在 MyBatis 执行器上的,它们能拿到参数、改写 SQL、拦截执行过程;而 Druid filter 作用在 JDBC 连接层,面对的是最终要发送给数据库的 SQL 文本。两者可以共存,各管一段。

4.2 WallFilter:SQL 防火墙调优

WallFilter 的价值不只是防 SQL 注入,它更像个“业务 SQL 守门员”。很多团队会不小心把允许事务里用分号拼接多条 SQL 的习惯带到生产环境,一旦遇到 WallFilter,默认会直接报错。我给你整理了几个最常调整的开关:

配置项默认值作用典型调整场景
multiStatementAllowfalse是否允许一个 Statement 执行多条 SQL批量初始化脚本需要执行多条语句时打开
noneBaseStatementAllowfalse是否允许非基础语句(如 SHOW、USE、SET)某些运维工具连库时会被拦,可临时放行
deleteAllowtrue是否允许 DELETE 语句业务如果只有逻辑删除,想彻底禁物理删除可设为 false
updateAllowtrue是否允许 UPDATE 语句同理,可做只读库保护
conditionAndTrueChecktrue是否检测 WHERE 1=1 这类恒真条件防误更新全表,强烈建议保持开启
selectWhereAlwayTrueChecktrue是否检测 SELECT 恒真条件防拖全表的恶意查询
insertAllowtrue是否允许 INSERT只读报表库可设为 false
commitAllowtrue是否允许显式提交防止业务代码里乱写 commit

在 dynamic-datasource 的 yml 里,WallFilter 的配置可以这样放:

spring: datasource: dynamic: druid: filter: wall: enabled: true config: multi-statement-allow: false none-base-statement-allow: false delete-allow: true update-allow: true condition-and-true-check: true

强调一下 multiStatementAllow。很多初级开发会用 MyBatis 的 foreach 拼接批量插入,但 MyBatis 最终发送的仍然是一条 SQL,所以和这个开关没有关系。真正会被 multiStatementAllow 拦截的是直接通过 JDBC 执行包含分号的“多重语句”。如果你在配置中心或初始化 SQL 工具里需要一次执行多条语句,再单独开,不要全局放开。

4.3 StatFilter:慢 SQL 统计

StatFilter 是监控数据的数据源。没有它,Druid 监控页里的 SQL 列表就是空的。常用的全局配置:

spring: datasource: dynamic: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 3000 merge-sql: true

slow-sql-millis 设置为 3000,表示执行时间超过 3 秒就会记成慢 SQL,会在日志里输出。merge-sql 开启后会合并结构相似的 SQL,例如同一个 Mapper 方法因为参数不同产生的多条记录会聚合成一条统计,避免监控列表被刷屏。

StatFilter 和 WallFilter 的开启方式都是一样的,但注意别重复注册。如果你自己在配置类里 new 了一个 StatFilter,yml 里又开了 filter.stat,会看到重复统计甚至异常。同一个数据源上的同类过滤器,只保留一种注册方式。

4.4 日志过滤:别在生产环境打印全量参数

日志类过滤器我建议谨慎开启。Slf4jLogFilter 能把执行的 SQL 和参数值全部打印出来,本地调试确实好用,但生产环境一旦打开,日志量会大得吓人,而且可能把用户手机号、身份证这类敏感数据刷到日志平台里。

真要排查线上问题,我通常配合 log-slow-sql 单独看慢 SQL 日志,而不是用 SQL 全量日志。如果非要在某个环境临时开启,只对 Stderr 或单独一个 logger 输出,避免大量日志把磁盘写满。

4.5 别忘了 MyBatis-Plus 侧还有自己的拦截器

既然标题里提到 SQL 过滤器,我想把边界说清楚:MyBatis-Plus 的“拦截器”和 Druid 的“过滤器”是两个体系。MyBatis-Plus 自带 PaginationInnerInterceptor 做分页,OptimisticLockerInnerInterceptor 做乐观锁,还可以自定义 InnerInterceptor 做权限数据过滤。

比如“数据权限”这种需求,典型的做法是写一个自定义 InnerInterceptor,在 SQL 执行前根据当前用户注入部门 ID 或租户 ID 的查询条件。这种过滤发生在 SQL 还未进入 JDBC 层之前,做的是 SQL 改写;Druid 的 WallFilter 发生在 SQL 已经成型之后,做的是安全检查。两者配合,业务层的权限过滤加上连接层的安全拦截,才能把 SQL 管理做完整。

5. @DS 切换细节与 XML 同目录配置

5.1 @DS 注解的正确放法

数据源切换的入口是 @DS。最典型的用法是加在 Service 实现类或方法上:

@Service public class OrderServiceImpl implements OrderService { @Override @DS("slave") public List<OrderVO> listOrders() { return orderMapper.selectList(...); } @Override @DS("master") public void createOrder(Order order) { orderMapper.insert(order); } }

@DS 支持放在类上,也支持放在方法上,方法上的优先级高于类。如果类上标了 @DS("master"),方法上标了 @DS("report"),实际走的是 report 库。另外它也可以放在 Mapper 接口或 Mapper 方法上,但我建议还是统一放在 Service 层,因为数据源路由属于业务边界,放在 Mapper 会让调用方不知道当前到底走了哪个库,排查链路会变麻烦。

5.2 切换失效的根源:自调用和事务顺序

@DS 的底层切面依赖 Spring AOP,所以它和 @Transactional 一样,对同类内部调用无能为力。如果你在 OrderServiceImpl 的一个方法里直接通过 this 调用另一个带 @DS 的方法,注解根本不会触发,数据源切换可靠度为零。要避免这个问题,可以把数据源不同的操作拆到不同的 Service Bean 里互相调用,或者用注入的 Mapper 方式绕开自调用。

事务边界的问题更隐蔽。Spring 的事务管理器在开启事务时就会把数据源绑定到当前事务上下文。如果在 @Transactional 方法内部再通过 @DS 切换数据源,dynamic-datasource 的实现虽然会尽力帮你处理,但从语义上说,事务一旦开始,连接已经确定,这时候再切换数据源很容易出现“你还是跑在旧库”或者“连接被拿到了事务上下文之外”的现象。

我的经验法则是:先切库,再开事务。把 @DS 和 @Transactional 尽量放在不同方法上,让切库发生在事务边界之前。如果有跨库的强一致需求,涉及分布式事务,那就不是动态数据源能单方面解决的了,需要用 Seata 或本地消息表这类方案。

5.3 XML 与 Mapper 同目录的配置方法

这个点同时也是热搜里出现率很高的问题。很多人习惯把 Mapper 接口放在 com.xxx.mapper 包下,然后 XML 也放在同一个包目录里,看着整齐,但项目一打包,XML 文件根本不存在 target 目录里,运行时直接报 Invalid bound statement。

原因是 Maven 默认只把 src/main/resources 下的文件当作资源,src/main/java 下的文件只编译 .java,不会复制 XML。解决办法有两个,二选一或者两个都做。

第一个办法是把 XML 放进 resources 目录下的同一路径,比如接口在 com/xxx/mapper/UserMapper.java,XML 就放在 src/main/resources/mapper/UserMapper.xml,然后配置 mapper-locations:

mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml

第二个办法是保留 XML 和 Mapper 接口在同一个包下,但要主动告诉 Maven 把这个目录下的 XML 也打包进去:

<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>

两种方式我都用过。如果追求代码阅读时一眼能看到接口对应的 XML,第二种更直观;如果你有大量分模块或者多 Maven 模块结构,建议用 classpath* 配合第一种,因为它在多模块场景下能扫描到所有 jar 里的 mapper XML,不会漏掉依赖模块里的映射文件。

5.4 手动切换数据源的方式

注解用多了难免遇到动态场景,例如一个方法里要根据某个配置字段决定走哪个库,@DS 注解没法在运行时变。这时候可以用 dynamic-datasource 提供的上下文 API:

DynamicDataSourceContextHolder.push("report"); try { reportMapper.querySummary(); } finally { DynamicDataSourceContextHolder.poll(); }

push 和 poll 必须成对出现,忘记 poll 会把路由状态留在当前线程,下一个请求持续串库。这种写法适合极少数迫不得已的场景,数量一多就要考虑抽象成策略类,不然代码里到处是 push/poll,维护起来很痛苦。

6. 常见问题速查与避坑整理

6.1 高频问题排查表

我把自己在实际项目里遇到过的典型问题整理成了表格,按“现象 - 原因 - 处理”三条列出来,遇到类似问题可以直接对号入座。

现象可能原因处理方式
@DS 不生效,SQL 始终走主库同类自调用导致 AOP 没触发拆 Service、注入代理对象,或通过 Mapper 强制切库
事务方法里切库失败事务开启时连接已绑定原库调整方法边界,让 @DS 在 @Transactional 之前执行
多线程下数据源串了线程池复用时路由上下文未清理尽量用注解方式,避免手动 push 忘记 poll
Druid 监控页打不开或 0 数据StatFilter 未开启,或 StatViewServlet 未注册yml 开 filter.stat,再注册 StatViewServlet
启动报密文解密失败connect-properties 未传给 DruidDataSource检查 config.decrypt 和 config.decrypt.key 是否在正确节点
WallFilter 一直拦截自己的 SQLSQL 里包含多条语句或非基础语句单独调整对应开关,或加白名单规则
XML 找不到 bound statementXML 没被 Maven 打包到 classpath配置 mapper-locations 或增加 resource 打包规则
只有部分库 Druid 参数生效全局 druid 配置和数据源级配置冲突简化成只维护全局配置,单独库再覆盖

6.2 Spring Data JPA 和 MyBatis-Plus 怎么选

既然热搜里有这个问题,我多说两句。JPA 和 MyBatis-Plus 的本质差别在数据访问层的抽象方式。JPA 以“实体状态管理”为核心,对象关系映射做得彻底,实体一改,Hibernate 会自动同步数据库,适合业务规则强、领域模型重的项目。MyBatis-Plus 以“SQL 为中心”,把 SQL 的编写能力保留在开发者手里,适合对查询性能、复杂 SQL、存储过程依赖较多的项目。

在多数据源场景下,MyBatis-Plus 明显更有优势。原因有两个:一是 MyBatis 本身和数据库方言之间没有 JPA 那么厚的一层抽象,跨库切换时行为更可控;二是 dynamic-datasource 和 MyBatis-Plus 的配合生态已经非常成熟,几乎零成本接入。而 JPA 在多数据源场景里,EntityManager 和事务管理器的绑定关系更复杂,一旦出现跨库操作,排查成本比 MyBatis 高不少。如果团队更擅长 SQL,或者现有报表类型需求很多,直接选 MyBatis-Plus 是更稳妥的方向。

6.3 上线前容易返工的三个点

第一,数据库密码加密不能在最后一刻才想起来。Druid 解密需要公钥和密文配对,配置节点一旦写错,线上启动就是灾难。建议在联调阶段就把密文格式和 connect-properties 验证过,而不是等到部署前才处理。

第二,WallFilter 不要一上来就全局关闭。很多团队上线遇到莫名奇妙被拦截的 SQL,第一反应是直接在 yml 里把 wall.enabled 改成 false,这个操作等于把 SQL 防火墙裸奔。正确做法是定位到具体拦截原因,判断是不是业务必须的多语句或非基础语句,再针对具体开关做调整。

第三,监控页面开在生产环境一定要有鉴权。Druid 页面除了能看 SQL 还能看 Session 和内存信息,等于给攻击者提供了内部结构的地图。至少配登录账号和 allow 白名单,有条件再挂一层 Spring Security。

最后再说几句

这套配置我前后用了大半年,整体感受是 dynamic-datasource 加上 Druid 的组合已经很成熟,真正会翻车的地方几乎都集中在上文提到的 AOP 边界和过滤选择上。我现在新项目默认会把 WallFilter 打开再配一份慢 SQL 统计,监控页面只限内网访问,密码走 ConfigTools 加密。最后分享一个小技巧:如果你同时管理多个环境配置,建议把 druid 节点下的 connect-properties 单独拆成一个环境变量引用,这样同一套 yml 可以无缝在测试和正式环境间切换,不用每次上线前手工挑配置。踩过几次坑之后你会发现,多数据源配置的难点从来不在于“能不能连上多个库”,而在于连接池、事务和过滤规则这三者的边界是否清晰。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询