资讯详情

资讯详情

Spring Boot集成Druid连接池:监控、加密与防注入实战

写这篇文章之前先交代一个背景我做后端这几年Spring Boot项目凡是需要接数据库基本都绕不开连接池这一步。默认推荐的是HikariCP轻量、性能高没毛病。但一旦你的项目需要看慢SQL、需要防SQL注入、需要把线上数据库连接情况拎出来做分析HikariCP那套就显得太“素”了。这时候我第一反应就是Druid再配合druid-spring-boot-starter整个接入过程比我想象中顺很多。这篇文章不聊虚的直接基于我用druid-spring-boot-starter的真实经历从依赖引入、yml配置、监控页面、密码加密到生产环境的常见坑一条线讲完。同时也会照顾到Spring Boot版本差异毕竟最近不少朋友还在用Boot 2.x但已经有人开始踩Boot 3.x的坑了。适合刚接触Spring Boot对连接池一脸懵的新手也适合已经用上Druid但没把监控和防注入用起来的老手。读完你不会只会配个数据源而是能把这套东西真正用到生产环境里。1. 连接池选型为什么最终落到Druid上1.1 HikariCP、DBCP、Druid三者的定位差异先坦诚说一句Spring Boot 2.x以后官方默认数据源是HikariCP很多人觉得没必要换。HikariCP优点很明显——快字节码级别优化连接池大小默认10绝大多数CRUD场景完全够用。但它只解决连接复用问题不给你监控面板不给你SQL审计也不管SQL注入。DBCP在Spring Boot时代几乎没人用了配置繁琐性能还不如HikariCP除非是老项目维护否则不建议再碰。Druid的差异化能力在于“连接池之外”的部分内置监控统计、慢SQL记录、Web关联监控、SQL防火墙而且这些能力是原生自带不需要额外引入中间件。对一个要长期维护的系统来说能直接在页面上看SQL执行频率、识别慢查询、下线高危SQL比单纯追求那一点连接池性能更有价值。所以我的选择逻辑很简单性能差距在现代硬件上几乎感知不到但Druid多出来的运维能力是实打实能省事的。1.2 druid-spring-boot-starter帮你完成了哪些工作如果你用过早期Druid应该记得需要手动创建DruidDataSource、再new一个FilterRegistrationBean注册StatViewServlet、再new一个WebStatFilter注册WebStatFilter。这套样板代码每个项目复制一遍稍微漏一步监控功能就废了。druid-spring-boot-starter的出现就是把这些自动装配掉。starter里最核心的自动配置类是DruidDataSourceAutoConfigure它会在你引入依赖之后将DataSource的创建、初始化、监控Filter装配、Servlet注册全部接管。你在application.yml里写druid前缀的配置就可以控制连接池参数、监控规则、拦截粒度再也不用写一行Java配置类。这里注意一个前提spring.datasource.type要指到DruidDataSource或者完全不写type让starter的自动配置自己判断。如果你手动创建了一个DataSource的Bean反而会干扰自动配置的判断后面我会在踩坑部分细说。2. 先在项目里把依赖弄对别在第一步翻车2.1 Maven与Gradle的坐标写法Maven项目直接在pom.xml里加版本记得用你本地环境中能和Spring Boot兼容的版本。以我常用的1.2.20为例dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependencyGradle项目写法更简洁implementation com.alibaba:druid-spring-boot-starter:1.2.20这里提醒一下druid-spring-boot-starter内部依赖了druid的核心包所以不需要再单独引入druid重复引入反而可能出现类冲突。如果你之前项目里手动引过druid记得去重。2.2 Spring Boot版本“太高”导致启动失败的坑这条我要特意拿出来说因为见过太多人问“为什么我的项目一启动就报DataSource相关错误”结果一看Spring Boot 3.x。Spring Boot 3.x最大的变化是javax.servlet变成jakarta.servlet而Druid的老版本starter和1.2.x早期的核心包还在用javax命名空间直接导致类找不到或者服务无法注册。解决办法就是在1.2.20以上版本中选我实测下来1.2.20在Spring Boot 3.x上是稳定的当然Druid后续版本迭代也在持续适配。启动报错如果出现NoClassDefFoundError类定义找不到先别怀疑配置先查看一下项目里有没有tomcat-embed-jakarta相关依赖或者把Druid版本升到1.2.20以上再启动一遍。用Boot 2.x的朋友建议至少1.2.6以上因为早期版本存在一些连接池初始化时序问题。3. application.yml配置详解一份可以直接抄的配置3.1 数据源连接与连接池参数逐一说明先给一份我线上项目在用的核心配置去掉业务字段后长这样spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/my_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: my_password initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false每个参数背后的逻辑我按实战理解展开说。initial-size是启动时提前建立多少个连接设为5可以减少第一波请求打过来时临时建连的延迟min-idle是最小空闲数系统空闲时也保持至少5个连接避免流量突然进来时建连压力过大max-active是最活跃连接数20对于常规业务绰绰有余但要注意它不代表数据库能承受的并发上限如果后面接口出现连接等待优先看的是这个值是不是设小了。max-wait是获取连接的超时时间单位毫秒60000就是60秒超过则抛出获取连接超时异常。这个值不要设成-1否则在高并发下线程会无限等下去服务线程池直接被拖垮。time-between-eviction-runs-millis是空闲连接检测的周期设为60000意味着每分钟扫描一次min-evictable-idle-time-millis表示连接空闲多久之后可以被回收我这里的300000是5分钟。这两个参数配合起来目的是让数据库连接的回收不紧不慢对数据库侧的连接数控制也友好。test-while-idle设为true时连接池会在空闲检测时使用validation-query验证连接是否有效避免把已断开的连接交给应用test-on-borrow则是每次从池里拿连接都验证那太浪费性能通常关掉test-on-return同理归还时不验证。这里有一个铁律如果你的数据库是MySQLvalidation-query用SELECT 1就可以如果是Oracle最好改成SELECT 1 FROM DUAL如果数据库不支持这种写法会在空闲检测时报错这是排查连接问题的第一步。还有一个容易被忽略的配置项是connection-properties比如MySQL驱动在连接串里带了allowPublicKeyRetrievaltrue参数时你可以写进去。但这属于比较偏门的用法不建议新手一上来就搞这些先保持上述配置跑通再说。3.2 监控页面的两大核心组件StatViewServlet与WebStatFilterDruid的监控页面不是默认打开的需要配置StatViewServlet提供/actuator/druid或/druid路径下的监控HTML页面和WebStatFilter拦截Web请求做统计。配置文件里这样写spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 reset-enable: false allow: 127.0.0.1 deny: web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.css,*.gif,*.jpg,*.png,*.ico session-stat-enable: true profile-enable: true先解释几个让我踩过坑的字段。url-pattern的写法决定你访问监控页的路径比如写成/druid/*那访问地址就是http://你的地址:端口/项目上下文/druid/index.html。这里一定注意context-path前缀如果你的项目配了server.servlet.context-path: /myapp那完整的监控地址就是/myapp/druid/index.html很多人打不开页面就是忘了加前缀。login-username和login-password是访问监控页的基础认证设置之后进页面会先弹登录框。reset-enable设为false是为了防止页面上的“重置”按钮把统计数据清零。生产环境肯定要禁用这个按钮否则监控数据被人一键清空排查问题就失去依据。allow和deny是访问IP白名单和黑名单allow配置的是允许访问的IP多个地址用逗号分隔。默认情况下不配置allow时允许所有IP访问我建议生产环境一定要配或者至少保证登录口令不是弱口令。deny优先级高于allow在同一个IP同时出现在两个集合里时deny会生效。web-stat-filter是拦截Web请求并统计访问量的url-pattern配/*代表拦截所有请求exclusions里写的是不参与统计的路径比如静态资源和监控页自身不然请求监控页面的行为也会被监听既浪费性能又把统计数据搞脏。session-stat-enable开启后会统计Session维度的监控profile-enable开启后可以记录URL关联的SQL执行情况这俩对性能分析很有用。3.3 用于随机端口场景下的监控访问问题有一个热搜词是“springboot yml 随机端口”如果你在配置里写成server.port: ${random.int[8000,9000]}那每次启动服务端口都在变。用Druid监控的人会遇到一个麻烦书签里的监控地址端口对不上现在的地址。这不是Druid的问题是随机端口本身带来的。真想用随机端口又要看监控建议把端口日志打印清楚或者把监控页面地址在启动日志里输出。如果你更看重监控的稳定性那就把端口固定下来随机端口更适合临时环境。4. 数据库密码加密别把账号明文放在配置里4.1 用ConfigTools生成密钥对和密文很多项目直接把数据库密码写成明文放在application.yml里这在开发环境没问题但一旦配置被推到Git仓库等于把数据库钥匙交出去了。Druid提供了ConfigTools工具类可以对密码进行RSA加密然后把公钥和密文同时配置到yml里数据库连接时再解密。生成方式有两种。一种是在代码里临时执行public class DruidPasswordGenerator { public static void main(String[] args) throws Exception { String password 你的真实数据库密码; String[] keyPair ConfigTools.genKeyPair(512); System.out.println(privateKey: keyPair[0]); System.out.println(publicKey: keyPair[1]); System.out.println(password: ConfigTools.encrypt(keyPair[0], password)); } }另一种是直接用命令行工具把druid核心jar包下载下来以后执行java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools你的密码它会直接输出privateKey、publicKey和password三段内容。注意输出中的privateKey只用于本地生成密文生成完就可以丢了不要放进代码仓库配置文件只需要publicKey和加密后的password。配置里这样写spring: datasource: druid: username: root password: xxxxxxxxxxxxx密文xxxxxxxxxx public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJ等等等 connection-properties: config.decrypttrue;config.decrypt.key${spring.datasource.druid.public-key}4.2 配置加密后的常见错误最常见的问题是启动时报DataSource出错像“Public Key not found”之类。这不是加密本身出了问题而是你漏了connection-properties里的config.decrypttrue或者公钥没有正确传入。检查两个地方公钥有没有拷贝完整、有没有多余换行connection-properties里的key名是否正确引用了public-key。另一个坑是如果数据库密码本身很短RSA加密后密文很长有些人在复制时只复制了上半段导致密文丢失尾部字符启动解密失败。只要报错信息里出现Cipher相关异常第一反应就是去重新生成一组密钥再试一遍。5. 安全加固Wall Filter与SQL注入防御的实战配置5.1 WallFilter究竟拦截了什么Druid自带一个WallFilter是基于语义分析的SQL防火墙它不是简单地拿关键字匹配而是把SQL语句解析成语法树然后根据配置的规则判断是否合法。这意味着它能拦截很多常见的注入手法比如在WHERE条件后面拼接OR 11、利用注释符把后面的SQL注释掉、使用UNION SELECT窃取其他表数据等。启用WallFilter的方式很简单在yml里配置spring: datasource: druid: filter: wall: enabled: true config: multiStatementAllow: false noneBaseStatementAllow: false deleteAllow: true updateAllow: true insertAllow: true selectAllow: true关键在于几个规则项能不能理解透。multiStatementAllow默认false表示不允许一次执行多条SQL语句像“SELECT * FROM user; DROP TABLE user”这种直接会被拒掉noneBaseStatementAllow默认false表示不允许执行非基础SQL语句以外的操作比如执行存储过程等相关操作deleteAllow、updateAllow、insertAllow、selectAllow分别控制四类基础SQL是否能执行。很多团队为了避免误伤会把delete和update放开只关掉多语句和特殊调用这是折中方案但我个人建议如果系统没有批量更新需求updateAllow也可以谨慎关掉放到白名单里单独放行。还有几个高阶配置项值得补充比如conditionAndAllow和conditionOrAllow控制WHERE条件里是否允许AND和OR如果把OR关掉那“OR 11”这种注入方式直接在语法层面就被拦了。再比如minValue和maxValue能限制查询返回的数据范围对防止拖库也有帮助。不过这些配置在少数业务场景下会误伤比如合法的多条件OR查询被拦截需要结合实际调。5.2 配合Spring Boot全局过滤器处理异常输入再说一个网上比较多的问题就是Spring Boot全局过滤器处理上传PDF时怎么避免XSS攻击。这个问题看起来跟Druid无关但它和WallFilter的定位一致都是“在入口处拦截非法输入”可以放在一起说。XSS攻击的核心是把可执行脚本塞进HTML输出流里Druid的WallFilter不处理这个你需要在全局过滤器里对请求参数做HTML转义比如把和转成实体字符。但千万别对上传的PDF二进制流做这种处理否则文件内容会被破坏。区分方式很简单文本参数表单字段、JSON字段可以走转义过滤文件二进制流直接放行。Druid在这个链条里的价值是守住SQL层全局过滤器守住的是Web入口层两者配合才能形成有效的纵深防御这也是我做项目时比较强调的一层。6. 生产环境的监控配置与日志实践6.1 慢SQL统计的两种落地方式Druid的监控页面上能直接看到慢SQL但生产环境出问题不可能每时每刻盯着页面。推荐搭配慢SQL日志。Druid里有一个log4j或者slf4j记录的慢SQL日志开启方式是在配置里加上spring: datasource: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 1000slow-sql-millis设为1000表示执行时间超过1秒的SQL会被记录到日志里。注意这里的时间单位是毫秒别写成1否则所有SQL都会被视为慢SQL日志直接爆炸。配好之后你会发现日志里多出类似“slow sql: SELECT ...”这样的记录再配合分析工具定位问题就方便多了。另一种方式是把Druid的监控数据接入外部监控系统比如用Actuator的端点把Druid指标暴露给Prometheus或者定时把StatFilter的统计数据打印到日志。这块要看团队基础设施不强求。6.2 监控页面访问控制的正确姿势前面在StatViewServlet配置里提过allow和deny字段这里再补充一个容易被忽略的点生产环境一定要用内网IP访问监控页不要通过公网把/druid路径暴露出去。如果你所在网络环境不允许内网直接访问可以用跳板机或者把监控页绑定到本机地址后通过SSH隧道访问。这里不展开讲具体隧道方案核心原则是监控页不能裸奔。登录密码也很重要Druid监控页的登录会话默认是servlet session一段时间不操作就会失效这反而降低了口令被长期挂着的风险。reset-enable一定要是false否则任何打开页面的人都能一键清空统计值你排查历史问题时数据全部归零。6.3 应用重启后监控数据丢失的问题Druid的监控统计数据默认都存在内存里应用一重启数据就没了。你如果要做月度SQL性能趋势分析光靠控制台页面是不够的。要么定期手动从页面导出JSON或CSV要么自己写个定时任务把StatFilter的数据存储下来。我没用太重的方案就是在项目里加了一个定时任务每隔一段时间读取Druid StatManager的统计快照把关键指标写入业务库。采样频率不用太高5分钟一次足以不然数据量很大查询分析也变慢。有一点要提醒你读到快照的时候有些类的字段是内部实现的版本升级后字段名可能会变化所以采样代码里最好不要依赖具体实现类而是通过Druid统计API获取比如通过DruidDataSource的getStatValue方法取出连接数、活跃数等核心指标。7. 踩坑汇总版本、配置、多数据源等高频问题7.1 Spring Boot版本太高导致Druid失效前面提过一次这里做成完整排查思路。如果你确认自己引入了druid-spring-boot-starter但启动后日志显示数据源是HikariDataSource那大概率是自动配置没生效。一个典型原因就是Spring Boot 3.x配合了旧版Druidstarter的自动配置条件没有满足。先去maven仓库看druid-spring-boot-starter的版本Boot 3.x选1.2.20以上其次看项目里有没有显式声明了DataSource的Bean如果有先去掉再看是不是把spring.datasource.type写成了别的类型。按这三步排查基本能解决。7.2 数据库连接URL带?参数时的解析问题“springboot 数据库驱动 ?参数”是热搜里的一条这类问题通常在配置了带参数的数据库URL时遇到比如jdbc:mysql://localhost:3306/mydb?useSSLfalseserverTimezoneUTC。Druid在解析连接串时不会丢失这些参数问题往往出在你自己写URL时漏了某个驱动需要的参数于是数据源初始化时直接报错。比如MySQL 8.x的驱动就要求必须指定serverTimezone否则会报SQLException。建议先单独用JDBC原生驱动测试这条连接串能否连通能连通再放到Druid配置里这样能快速区分是不是Druid本身的问题。另外连接池里有些参数比如connectTimeout和socketTimeout也可以追加到URL上但注意值单位是毫秒。7.3 多数据源项目启动报错DataSource自动配置冲突如果你项目里配置了两个数据源比如一个业务库一个报表库Druid自动配置默认只会处理main下唯一的数据源另一个库要自己定义DruidDataSource的Bean。这个过程经常出现“DruidDataSourceAutoConfigure”尝试接管两个数据源导致冲突的情况。解决方法是在自定义数据源的配置类上排除自动配置做法是在启动类上取消druid数据源的自动配置SpringBootApplication(exclude DruidDataSourceAutoConfigure.class)或者如果你不需要starter的自动装配可以完全不用druid-spring-boot-starter手动创建一个DruidDataSource的Bean再手动配置StatViewServlet和WebStatFilter。两种方式我都用过单数据源无脑用starter多数据源建议第二个及之后的数据源手动创建第一个主数据源继续用starter减少冲突面。7.4 Linux服务器上监控页面打不开开发环境Windows下访问监控页一切正常部署到Linux服务器以后页面白屏或者404这种问题多半不是Druid配置问题而是项目部署方式不同导致Servlet路径没对上。如果是Docker部署要确认你映射端口的同时有没有把容器的context-path考虑进去如果是Spring Boot的fat jar直接java -jar启动再检查url-pattern是不是/druid/*并确认访问地址里有没有带项目上下文。最简单的定位方法项目启动日志里会打印所有已注册的Servlet映射路径搜索druid关键字看到注册路径后照着访问肯定能通。7.5 log4j依赖冲突导致启动异常最后说一个比较隐蔽的坑druid-spring-boot-starter在某些版本下会依赖log4j相关组件如果你的项目还同时引入了logback或者log4j2的另一个版本启动时可能抛出多个SLF4J绑定错误或者出现logger创建异常。解决方案是检查依赖树把druid传递进来的不需要的日志依赖排除掉保留项目统一的日志实现。具体命令是mvn dependency:tree配合grep或者在IDEA的Terminal里直接执行找到druid相关的传递依赖后在pom里用exclusions排除冲突项。这个问题不一定会百分百触发但遇到了要能识别出来别总在配置上反复折腾。8. 最后分享一点使用进阶Druid在国产化数据库场景里的适配热搜里有人提到“金仓读写分离配置”顺带说一句我实际遇到的类似场景。国产数据库如金仓、人大金仓KingbaseES、达梦等本质上仍兼容PostgreSQL或Oracle的JDBC协议Druid的适配重点在driver-class-name和validation-query。你只需要把driver换成对应的驱动类比如KingbaseES的驱动类名通常是com.kingbase8.Driver或者cn.com.omc.gbase.Driver再把validation-query改成SELECT 1 FROM DUAL其他连接池参数可以保持不变。读写分离在Druid里可以通过proxy-filter或者内置的多数据源方案实现不过说实话Druid的处理能力有限真正的读写分离建议交给数据库中间层。如果只是想做到读走备库、写走主库在应用层配置多个数据源并配合Transactional和ReadOnly注解比强行在Druid里搞更清晰。另一个小技巧是Druid的spring-boot-starter会自动注册actuator端点Spring Boot 2.x环境下你可以通过/actuator/druid查看一些指标数据。但要注意暴露端点这件事本身也有安全风险生产环境还是要把actuator的访问权限控制好跟监控页一个待遇。我在实际项目中用的是内网IP白名单加Basic Authentication双重限制效果还不错。说回整体使用感受druid-spring-boot-starter不是那种“装上就完事”的依赖它的价值需要靠配置和运维习惯一点点体现。密码加密、SQL防火墙、慢SQL日志、监控页面权限这几块如果全部落地数据库出问题时你的排查速度会快很多。至少我在一次线上慢查询问题中就是靠Druid监控页里的慢SQL列表定位到一条没有索引的模糊查询十几分钟就锁定了问题SQL换成HikariCP估计得翻半天业务日志加审计记录效率完全不同。这也是我为什么在Spring Boot项目里坚持用它的原因——开发时多花十分钟配置运维时会帮你省十个小时。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →