
简介这是一份面向Java后端开发者的多模块Web应用整合示例围绕项目构建、自动配置、持久层映射和数据库连接池四个环节演示Maven、SpringBoot、MyBatis与Druid的协作方式。压缩包共99个文件大小约175KB包含Java源码、编译后的class文件、XML映射与Spring配置、properties全局配置、Eclipse工程描述文件以及SQL脚本并按parent、common、entity、dao、service、web划分Maven子模块便于对照真实项目结构来学习。目前已有381人学习浏览适合正在搭建SpringBoot数据访问层或希望优化数据库连接性能的开发者参考。通过该工程可以快速理解在父pom中统一管理依赖版本、在配置文件中启用Druid并设置连接参数、通过Mapper接口与XML完成数据操作等方法同时还能借鉴多模块划分的规范提升项目的可维护性与扩展性。无论是初学整合流程的开发者还是需要统一工程结构的团队都能从中获得可复用的设计思路。1. 技术选型背后的逻辑为什么这套组合是 Java 后端的事实标准先聊聊这套技术栈的定位。Maven Spring Boot MyBatis Druid 的组合在国内 Java 后端项目里的普及率高得惊人。你能在招聘 JD 上看到它能在外包项目里看到它也能在中小型公司的核心业务系统里看到它。为什么是这四个凑在一起因为它们刚好覆盖了一个后端服务从“构建”到“运行”到“数据访问”再到“连接管理”的完整链路。Maven 管的是依赖下载、项目构建和打包Spring Boot 管的是应用启动和自动装配MyBatis 管的是 SQL 和 Java 对象之间的映射Druid 管的是数据库连接池、SQL 监控和防注入。四个工具各司其职配合起来非常顺。有人问我为什么不用 JPA 而用 MyBatis我的回答很简单国内大部分业务系统的 SQL 复杂度高、优化诉求强MyBatis 这种把 SQL 控制权完全交给开发者的方式在排查慢查询、做 SQL 调优时更直接。JPA 虽然开发效率高但一旦遇到复杂查询生成的 SQL 不好控制反而不利于性能调优。这套组合适用于什么场景简单说只要你的项目需要连接关系型数据库、需要对外提供 REST API、需要做连接池监控就可以用它。最适合两类人一类是刚入行想快速上手一个完整后端项目的初级开发另一类是需要在公司内部快速搭建一套后台管理系统或微服务模块的中级开发。2. 搭建前的关键决策版本选型和 Maven 环境优化2.1 Spring Boot 版本不是越高越好很多新手一上来就拉最新的 Spring Boot 3.x然后被各种兼容性问题折磨得欲哭无泪。我个人的经验是如果团队里大部分人用的是 JDK 8就老老实实用 Spring Boot 2.7.x如果已经迁移到 JDK 17 以上再考虑 Spring Boot 3.x。为什么因为 Spring Boot 3 基于 Jakarta EE 9包名从 javax.* 改成了 jakarta.*MyBatis、Druid、PageHelper 这些第三方库如果版本没跟上就会报 ClassNotFoundException 或者 NoSuchMethodError。以我近期在维护的一个老项目为例当初用的是 Spring Boot 2.3.4.RELEASE MyBatis 3.5.5 Druid 1.1.22这套组合在 JDK 8 下跑了三四年一直没有大毛病。后来有人把 Spring Boot 升到 3.1.5结果 MyBatis-Spring-Boot-Starter 需要 3.0 以上的版本Druid 也需要 1.2.18 以上才支持 jakarta 命名空间折腾了一整天才把坑填完。所以我的建议是非必要不升级升级前先查兼容矩阵。2.2 Maven 镜像仓库配置国内开发者的必修课Maven 默认从中央仓库下载依赖在国内那速度简直是灾难。之前有个同事在 eclipse 里做 Spring Boot 集成 MyBatis一直卡在 downloading 状态其实就是中央仓库网络不稳定导致的。解决办法是在 Maven 的 settings.xml 里配置阿里云镜像这个操作几乎成了国内 Java 开发的标配动作。mirrors mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrorscentral 表示镜像只拦截中央仓库的请求不影响你自己在 pom.xml 里指定的其他私有仓库。如果你所在公司有私服Nexus 或 Artifactory可以把私服地址加到 pom.xml 的 distributionManagement 和 repositories 里然后把 settings.xml 里的 mirrorOf 改成 *让所有依赖都走私服私服没有的再回源拉取。这样既能加速下载又能统一管理团队依赖版本。2.3 JDK 编译版本必须锁死Maven 项目里最经典的坑之一就是编译版本不一致。你本机用 JDK 8 编译CI 机器上跑的是 JDK 11产出的 class 文件版本就不一样部署到服务器上就可能报 UnsupportedClassVersionError。解决办法是在 pom.xml 里显式指定编译版本properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties注意 source 和 target 都设成 1.8只设 source 不设 target编译时可能还是会用当前 JDK 的默认版本。3. 核心配置实操pom.xml 与 application.yml 的逐行拆解3.1 依赖引入一个都不能少我创建一个整合项目时pom.xml 里通常会这样写核心依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies这里有几个容易踩的坑。mysql-connector-java 在 Spring Boot 2.7 里默认版本是 8.0.33不需要手动指定版本但如果你用的是 MySQL 5.7 及以下连接 URL 里要加 useSSLfalse否则控制台会刷一堆 SSL 警告虽然不影响运行但很烦。还有一个坑是 druid-spring-boot-starter 的版本选择我遇到过 1.2.6 和 Spring Boot 2.7 搭配时Druid 的监控页面打不开的情况换了 1.2.20 就好了。另外提一下 lombok这个虽然是个人偏好但确实能省不少事。加了 lombok 之后实体类里只需要写字段声明getter/setter 由注解自动生成代码量能少 60% 左右。我写示例项目时一般会加生产环境项目里如果团队统一意见也会加。3.2 Druid 数据源配置连接池参数不是随便填的Druid 作为连接池如果只是把它当普通的 DataSource 用那就大材小用了。它在生产环境的核心价值是监控、防注入和连接池治理。我之前在排查一个线上接口偶发超时的问题时发现就是连接池初始大小配得太小高峰期连接不够用Druid 的监控面板里能看到 active 数量飙到 20而 maxActive 才 20直接打满了后来调到 50 才缓解。下面是一份我常用的 Druid 配置模板spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: 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 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall,slf4j connection-properties: druid.stat.mergeSqltrue;druid.stat.slowSqlMillis5000 stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: your-strong-password allow: 127.0.0.1 web-stat-filter: enabled: true url-pattern: /* exclusions: *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*几个关键参数我单独说一下initial-size 和 min-idle应用启动时把连接池预热到 5 个连接避免第一个用户访问时再去建连耗时明显。test-while-idle 设成 trueDruid 会定时检测空闲连接是否有效无效的自动丢弃避免拿到死连接。test-on-borrow 设成 false每次从池里拿连接时不额外检测提升性能。这依赖上面的定时检测机制如果没有 test-while-idle建议把 test-on-borrow 设成 true。max-wait 设成 60000连接池满了之后等待 60 秒还是拿不到连接就抛异常防止线程无限阻塞。Druid 的监控页面配置里我把 allow 限制为 127.0.0.1也就是只有本机能访问 /druid/* 路径。这里要特别提醒一下生产环境务必改掉默认密码并且最好用防火墙限制监控页面的访问来源。网上一搜 Droid 弱口令漏洞基本都是说默认 admin/admin 可以直接登录监控页然后就能看到所有 SQL 和数据库连接信息敏感数据直接暴露。这个安全习惯要从第一个项目开始养成。3.3 MyBatis 核心配置驼峰映射和 SQL 打印MyBatis 虽然是 ORM 框架但它的理念和 JPA 完全不同SQL 是开发人员自己写的所以执行起来性能更可控。在 application.yml 里MyBatis 相关配置主要在 mybatis 前缀下mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl cache-enabled: falsemap-underscore-to-camel-case 这个配置强烈建议打开。数据库字段是 user_name实体类是 userName打开这个配置之后MyBatis 会自动把下划线转驼峰结果映射的时候就不用写 resultMap 了代码量瞬间减半。但是要注意如果实体类里的字段没有遵循驼峰命名规范或者数据库表里存在极端命名比如大小写混合这个自动映射可能失效这时候就需要在 XML 里显式写 resultMap 来兜底。log-impl 设置成 StdOutImpl 之后控制台会打印每一步执行的 SQL 和参数。这个只在开发环境用生产环境建议改成 org.apache.ibatis.logging.slf4j.Slf4jImpl 或者直接不配否则大数据量下 SQL 日志会刷爆磁盘。我之前有个同事在生产环境忘了关 SQL 打印结果日志文件一天涨了 5GB差点把磁盘打满。cache-enabled 设成 false 是因为 MyBatis 一级缓存默认开启二级缓存如果也开着在分布式环境下容易读到脏数据。如果是单机部署、数据一致性要求不高的场景可以打开二级缓存提升查询性能但大多数业务系统不建议开。3.4 Mapper 扫描和 SQL 文件位置在启动类或者配置类上需要加 MapperScan 注解来指定 Mapper 接口的扫描路径SpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }扫描路径写错了会报一个非常误导人的错误Invalid bound statement (not found)。这个错误出现的实际原因通常有两个一是 MapperScan 没有扫到 Mapper 接口二是 mapper-locations 配置的路径跟 XML 文件实际位置不一致。排查的时候先用 mvn clean 清一下 target 目录然后确认 XML 文件有没有被复制到 classes 目录下如果 resources 里根本没放 XML 文件那肯定找不到。我见过有人把 mapper XML 文件放在 src/java 目录下结果编译后根本没进 classpath找了一个小时才发现。正确做法是把 XML 放在 src/main/resources/mapper/ 目录下并保持文件名与 Mapper 接口同名比如 UserMapper.java 对应 UserMapper.xml管理起来最清晰。4. 从零到一跑通一个最小示例的完整过程4.1 建表与实体类假设我们要实现一个用户查询接口。数据库里建一张 user 表CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, user_name varchar(50) NOT NULL, age int(11) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实体类注意字段命名和数据类型对应特别是 Long 和 Integer 不要混用数据库里 bigint 对应 Java 的 Longint 对应 Integer。日期字段用 LocalDateTime 而不是 java.util.Date配合 Jackson 序列化时少踩很多坑比如时区偏移、格式不一致。4.2 写一个最简单的 Mapper 和使用场景public interface UserMapper { User selectById(Param(id) Long id); ListUser selectAll(); }对应 XML 文件?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper select idselectById resultTypecom.example.demo.entity.User SELECT id, user_name, age, create_time FROM user WHERE id #{id} /select select idselectAll resultTypecom.example.demo.entity.User SELECT id, user_name, age, create_time FROM user /select /mapper然后写一个 Controller 通过 UserService 调用 UserMapper一个最小的闭环就完成了。启动项目用 Postman 请求 /api/user/1就能从 MySQL 里读到数据并返回 JSON。4.3 验证监控页面和 SQL 打印是否生效项目启动后浏览器访问 http://localhost:8080/druid/index.html输入之前配置的账号密码进入 Druid 监控面板。重点看三个 Tab数据源、SQL 监控、URI 监控。数据源 Tab 能看到连接池的初始大小、活跃连接数、空闲连接数SQL 监控 Tab 能看到每一条执行过的 SQL、执行次数、平均耗时、最大耗时URI 监控 Tab 能看到每个接口的被访问情况和响应时间分布。如果你请求一次用户接口再刷新 SQL 监控 Tab能立刻看到那条 SELECT 语句的记录这说明整个链路已经通了。控制台也会打印出 SQL 语句和执行参数。到这一步Maven Spring Boot MyBatis Druid 的最小整合就算成功跑通了。5. 实战中高频踩坑与排查思路5.1 Spring Boot 版本太高导致的兼容性问题热搜词里“springboot版本太高”这个关键词我太有感触了。Spring Boot 3.0 刚出那阵子很多人跟着官方文档升级结果发现自己的 MyBatis、PageHelper、Druid 全都要换版本。更麻烦的是如果项目里用了 Velocity、FreeMarker 这些模板引擎老版本的 starter 根本不支持 jakarta 命名空间。我的排查思路是三步走先确认当前 Spring Boot 版本执行 mvn dependency:tree 查看依赖树看看哪些包存在版本冲突。去 Maven 中央仓库搜索对应 starter 的最新版本确认它是否支持当前 Spring Boot 的基线版本。如果确实要升 Spring Boot 3.x优先升级 MyBatis 到 mybatis-spring-boot-starter 3.0.3 以上、Druid 到 1.2.18 以上。5.2 控制台明明打印了 SQL 但结果集全是 null这个问题的根源基本是驼峰映射没开或者 resultType 指向的实体类没有无参构造方法。检查顺序先确认 map-underscore-to-camel-case 是否为 true再确认实体类里有没有默认的无参构造函数。如果用了 Lombok 的 Data 注解默认会生成无参构造一般没什么问题。如果实体类手动写了带参构造但忘了写无参构造MyBatis 反射创建实例时就可能报错或者返回 null。5.3 Druid 监控页面访问 404 或者报 Forbid 提示访问 404先检查 pom.xml 里引入的是 druid-spring-boot-starter 还是 druid。前者才有自动配置后者只是个裸连接池需要自己手动写配置类才能开启监控。如果报 Forbid 或者 Access denied 这样的提示说明 stat-view-servlet 里的 allow 白名单把你当前 IP 挡住了检查 allow 配置或者在 deny 里加上你当前访问 IP。5.4 Druid 什么情况下会关闭 Statement这个问题在热搜词里有很多人在排查连接泄漏时遇到过。Druid 默认会在连接归还给连接池时关闭该连接上所有未显式关闭的 Statement这是 Druid 的默认行为目的是防止连接泄漏。如果你的业务代码里写了类似这样的逻辑PreparedStatement ps connection.prepareStatement(sql); ResultSet rs ps.executeQuery(); // 没有关闭 rs 和 ps那么当连接归还到连接池时Druid 会关闭这些 Statement。正常情况下这是好事但如果你的代码里在 Statement 关闭之后还试图通过它获取数据就会报 Statement is closed 错误。正确的做法是自己在 finally 块里关闭 ResultSet、PreparedStatement、Connection顺序和创建顺序相反。5.5 事务不生效Transactional 使用注意MyBatis-Spring-Boot-Starter 默认会配好事务管理器Transactional 可以直接用在 Service 方法上。但有一个容易忽略的点Transactional 默认只回滚 RuntimeException 和 Error如果你在方法里 catch 了异常并手动把异常信息记录到日志里然后再抛出受检异常事务不会回滚数据就会被错误地提交。我之前就是因为一个业务校验失败抛了 Exception结果数据库里插入了半条脏数据。解决办法是在 Transactional 里指定 rollbackFor Exception.class并且不要在 try-catch 里吞掉异常。5.6 MySQL 8 驱动和时区问题如果用 MySQL 8连接 URL 建议显式加上 serverTimezone 参数比如jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai不写 serverTimezone有些驱动版本会报 SQLException: The server time zone value Öйú±ê׼ʱ¼ä 或者乱码。另外 characterEncoding 一定要写 utf8注意别写成了 utf-8带横线会在部分环境下触发驱动解析问题导致中文乱码。6. 一整套配置的收尾建议这套组合跑通之后我通常会在项目里再加两样东西一个是 Maven 的 profile 配置用 定义 dev、test、prod 三套环境分别对应不同的 application-{env}.yml 文件这样打包的时候一条命令就切换环境不用每次手改数据库地址。另一个是 Maven 的 maven-surefire-plugin 配置把 test 阶段默认的单元测试跳过或按需执行避免在 CI 上因为环境问题导致打包失败。另外说一个经验之谈如果你在公司里做的是长期维护的项目建议把 Maven 仓库从默认的 ~/.m2 迁到非系统盘比如 D:\maven-repo 或者 /opt/maven-repo并把 settings.xml 里的 localRepository 指过去。这样系统盘重做、IDE 缓存清理都不会把辛苦积累的依赖包冲掉。我自己就吃过这个亏笔记本系统一盘 C 盘满了清理时误删了 .m2 目录结果整个项目的依赖全部重新下载了一遍整整浪费了一个下午。最后再分享一个小技巧排查 Maven 整合类问题时全局搜你的项目里是否存在多个版本的同一个依赖最快捷的方式是执行 mvn dependency:tree -Dverbose它能列出每个依赖的完整版本树一眼就能看到哪个包的版本被顶掉了。依赖冲突大多数时候不会直接报错而是导致某个类的行为不符合预期这种问题在 MyBatis、Druid 这种涉及字节码和代理的框架里尤其常见。先排查依赖树再谈功能调试能少走很多弯路。本文还有配套的精品资源点击获取