SpringBoot+JDBC+MySQL完整工程模板:从环境搭建到CRUD实战

发布时间:2026/9/7 6:34:53
SpringBoot+JDBC+MySQL完整工程模板:从环境搭建到CRUD实战 简介面向Java开发者的SpringBoot通过JDBC连接MySQL整套实战资料包尤其适合刚接触SpringBoot数据访问层、想在真实项目中快速落地的初中级读者。资源以“源码安装包驱动组件”形式组织场景覆盖环境配置、数据库连接、基础增删改查和常见异常处理能帮助读者省去自行搜集依赖、版本匹配与排错的大量时间。压缩包共136个文件、约637.42MB主要包括Java源码与编译后的class文件、Spring配置文件xml/properties、SQL建表与初始化脚本以及大量dll动态库和exe/msi安装包等MySQL相关驱动与部署组件目录结构完整便于按需取用。已有598人学习下载。借助示例控制器、实体类及配套SQL读者可直接运行工程观察实际连接与查询效果进而掌握SpringBoot整合JDBC的配置要点和DAO层编码习惯。 先把这个项目的使用价值和场景说清楚它无关什么高深莫测的黑科技也没有需要背一整天的复杂配置。它就是一套我实际调试通过的 SpringBoot 结合 JDBC 直连 MySQL 的完整工程模板从 MySQL 8.0 的安装包、JDK 环境变量配置、IDEA 中的项目创建到最终 Postman 里能够真实地拿到数据库里查出来的数据整条链路全部打通。适合刚学完 Java 语法、准备迈向真实项目开发却被“数据库驱动加载失败”、“时区报错”、“连接被拒绝”这些乱七八糟的问题反复折腾的初学者也适合那些需要快速给老项目或者非 MyBatis 项目搭建数据访问层的老手做参考。一句话这是个可以拿来直接跑的基线版本不用再看碎片化教程拼积木。1. 项目全景与实际落地效果我建议你先别急着研究代码细节因为我见过太多人最后项目跑不起来不是代码本身写错了而是根本不知道自己写的东西应该在哪个环节生效。先花两分钟把这个项目的全貌看清楚后面你再动手的时候心里会有底得多。这个项目的核心链路是HTTP 请求 → SpringBoot 控制器(Controller) → JDBC Repository 仓库层 → MySQL 数据库。也就是说浏览器或者 Postman 发一个 GET 请求到本地 8080 端口的某个路径上SpringBoot 里面会有一个类负责接收这个请求然后它转身去调用一个专门操作数据库的类这个类再用 JDBC 原生的 API 去跟 MySQL 服务器通信把结果查出来一层层原路返回最终变成 JSON 数据展示给你。我把项目跑起来之后实际测试过的完整流程记录在这里你可以对着这个预期结果来反向校准你的环境从创建项目到接口返回数据整个过程的耗时大概取决于你的网速和机器性能。第一次运行 SpringBoot 需要从 Maven 中央仓库拉取大概几十 MB 的依赖包这部分时间不算在编码里属于正常等待。接口返回的数据格式是标准的 JSON比如项目里预置的查询所有用户列表功能最终会在浏览器里看到一个数组里面包含 id、name、email 这些字段我实测过中文数据在 JDBC 驱动 8.0 版本下不需要额外的编码处理就能正确显示。为了让这套方案真正完整我把自己用的 JDK 8、MySQL 8.0 安装包以及整份工程源码都整理在了一起。你看到这篇文章的时候不需要再跑到 Oracle 官网或 MySQL 官网费劲找下载入口在配套的分享目录里直接拿就行。我需要提醒一句MySQL 8.0 和 5.x 版本的连接方式有一点不同主要差别在驱动类名和连接串的时区参数上后面我会专门讲这个区别这也是老教程最容易坑你的地方。2. 环境准备JDK、MySQL 8.0、IDEA 的安装与配置一套能跑起来的 Java Web 项目环境变量的作用是全局的引路标识。如果你机器上已经装过 JDK 8 以上版本并且java -version能正常输出版本号那么第一步可以直接跳过。但如果是新电脑或者是第一次在 Windows 上折腾 Java 开发这一步要认真看因为后面所有报错里有相当一部分的根源其实是环境变量没配好导致找不到命令而不是代码本身的问题。2.1 安装 JDK 与环境变量的逻辑Windows 上安装 JDK 基本就是一路 Next但我强烈建议你在安装路径里不要出现中文和空格比如C:\Java\jdk1.8.0_202这样的路径就很稳妥后续有些老版本框架对带空格的路径处理得并不友好。装完之后需要配置三个系统变量它们是JAVA_HOME、Path和CLASS_PATH。为什么一定要配置JAVA_HOME因为很多中间件包括 IDEA、Maven、Tomcat它们默认都是通过读取这个环境变量来寻找 JDK 的。你只把java.exe所在目录加进 Path 是治标不治本安装的软件一多就乱了。具体操作是在系统环境变量里新建JAVA_HOME变量值填你 JDK 的安装根目录例如C:\Java\jdk1.8.0_202然后在 Path 变量里新增一条%JAVA_HOME%\bin让系统能在命令行里找到编译和运行命令最后新建CLASS_PATH变量值为.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar这个点号代表当前目录Java 运行时会在当前目录下搜索类文件。配置完成后按 WinR 输入cmd打开命令行输入java -version能显示版本号再输入javac -version也能正常显示版本号说明编译环境已经就绪。这里值得多说一句java命令能执行不代表能编译很多人的机器上只有 JRE 没有 JDK输入javac会报“不是内部或外部命令”所以两个命令都要验证。2.2 MySQL 8.0 安装中的两个关键决策MySQL 安装我推荐用免安装的 ZIP 压缩包版本不建议用那种图形化安装向导。原因很简单图形化安装向导会附带一些你用不上的组件占内存而且在 Windows 上它自动配置的 my.ini 文件不一定符合你的需求出了问题反而难排查。ZIP 包解压即用所有配置自己掌控这对初学者理解数据库的启动原理更有帮助。解压 MySQL 之后进入解压目录里面有个my.ini文件需要手动创建有时压缩包自带my-default.ini建议复制一份改名为my.ini。这个配置文件里最核心的是端口号和字符集我实测用的配置提供了这几个必要项[mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 basedirD:/mysql-8.0.33-winx64 # 设置mysql数据库的数据的存放目录 datadirD:/mysql-8.0.33-winx64/data # 允许最大连接数 max_connections200 # 服务端使用的字符集默认为8比特编码的latin1字符集 character-set-serverutf8mb4 # 创建新表时将使用的默认存储引擎 default-storage-engineINNODB [mysql] # 设置mysql客户端默认字符集 default-character-setutf8mb4 [client] # 设置mysql客户端连接服务端时默认使用的端口 port3306 default-character-setutf8mb4critical: 注意basedir和datadir这两个路径的写法在 Windows 的 ini 文件中要么用正斜杠/要么用双反斜杠\\不能使用单个反斜杠否则会被当成转义字符导致 MySQL 无法启动。这个坑我测试时遇到过报错信息是“服务无法启动”或者提示找不到路径非常容易让人误以为是安装包损坏。数据目录data这个文件夹不要手动创建要交给 MySQL 自己初始化。配置完成后打开命令行窗口切换到 MySQL 解压目录的bin文件夹下先执行mysqld --initialize-insecure这个命令会在 data 目录生成系统数据库文件其中--initialize-insecure参数表示初始化后 root 用户密码为空方便本地开发直接登录。初始化完成后执行mysqld install将 MySQL 注册为 Windows 服务然后执行net start mysql启动服务。如果你在监听端口或者登录过程中能顺利通过现在就可以用 MySQL 自带的命令行工具测试登录了。执行mysql -u root -p因为密码为空直接按回车就能进入mysql命令行界面。到这里数据库服务就算真正准备完毕可以进行建库建表的操作了。2.3 IDEA 中的项目初始化设置IDEA 我用的是社区版它在 IntelliJ 官网可以直接免费下载功能上做 SpringBoot 开发社区版也完全够用。打开 IDEA 后用File → New → Project创建新项目左侧选择Spring Initializr如果网络稳定就直接用默认的start.spring.io在线模板服务如果加载慢可以换成阿里云的镜像地址在 URL 那一栏填https://start.aliyun.com套路是一样的。在创建页面上建议勾选 Spring Web 和 JDBC API 这两个依赖。Spring Web 是构建 RESTful 接口的基础JDBC API 是这里要用的数据库访问抽象层。MyBatis 这类框架不要在项目初始化阶段就引入这一篇要演示的是干净的 JDBC 原生方案减少干扰项。还有一个很多人忽视的地方就是项目 SDK 的选择。创建项目时IDEA 会自动检测到配置好的JAVA_HOME并填入 JDK 版本号。但有时候 IDEA 检测不到需要手动点击Add SDK选择 JDK 安装目录格式是C:\Java\jdk1.8.0_202这样的根目录。否则即使系统环境变量配置对了IDEA 内运行的 Maven 也会因为找不到编译环境报错。3. SpringBoot 中 JDBC 的三层解耦与核心配置SpringBoot 虽然极大地简化了配置但 JDBC 连接数据库这件事的本质没有变你终究要把驱动类加载进 JVM要告诉连接管理器在哪个 IP 和端口去连哪张库用哪个账号密码登录。SpringBoot 的价值在于它把这一套原本需要写一堆样板代码的操作压缩成了一个application.yml文件里的几个配置项以及一个已经封装好的JdbcTemplate类。3.1 驱动、连接串、账号名三件套在 SpringBoot 项目里你首先要明白pom.xml文件中需要引入哪些依赖坐标。除了最基础的spring-boot-starter-web还必须引入spring-boot-starter-jdbc这个是 Spring 对 JDBC 做的统一封装包它内部会自动传递引入一个数据库连接池。同时还要引入mysql-connector-java这个就是 MySQL 官方提供的 JDBC 驱动包注意版本要跟 MySQL 服务器版本大体匹配我用的是8.0.33。如果你用的是 MySQL 5.x那么驱动类名和连接串写法都不一样这一点在文末的避坑清单里会再强调一次。application.yml文件是整个数据库连接的枢纽SpringBoot 启动时读到这个文件里以spring.datasource开头的配置项自动帮你创建数据源、构建连接池并在需要时注入到你的代码里。我这里贴一份经过实测有效的最小配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码这里面的门道比表面看起来多得多。driver-class-name光看名字就很容易理解它指定了驱动类的完整类名MySQL 8.0 以后必须写com.mysql.cj.jdbc.Driver而老版的com.mysql.jdbc.Driver已经标记废弃虽然还能用但是会刷出一大段警告日志。url是连接数据库的地图路径jdbc:mysql://是协议头localhost:3306是数据库服务的地址和端口test_db是具体的数据库名称。后面跟的那一长串参数useSSLfalse表示本地开发不启用 SSL 加密握手否则控制台会有红字警告serverTimezoneAsia/Shanghai是解决时区报错的关键参数MySQL 8.0 默认时区是 UTC如果不指定驱动会认为服务器时区未知直接抛异常。实战排错如果你在启动后看到类似The server time zone value 乱码 is unrecognized这种报错100% 是serverTimezone参数问题。另一个常见问题Public Key Retrieval is not allowed解决方式是在连接串后面追加allowPublicKeyRetrievaltrue这个参数在 MySQL 8.0 版本中默认是关闭的但本地用 root 账号配合空密码或简单密码连接时经常需要手动打开。3.2 配置数据源与理解 Pool 参数的取舍除了上面三个最核心的配置application.yml里还有一组hikari连接池参数可以选择性配置。SpringBoot 2.x 之后默认的数据库连接池是 HikariCP它在性能和稳定性方面表现非常优秀被社区称为“史上最快连接池”所以正常情况下你不需要再额外引入别的池依赖。连接池是什么概念打一个比方你开了一家餐厅如果每个顾客来都要现场新招一个厨师那不得把人累死连接池就是提前养着一批“厨师”顾客来了直接让空闲的厨师上用完厨师又回去待命这样避免频繁建立销毁数据库连接带来的性能开销。HikariCP 的核心配置项我建议这样设置spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 15 connection-timeout: 30000 idle-timeout: 600000minimum-idle表示池里最少保持 5 个空闲连接maximum-pool-size是池子里允许的最大连接数这是我经历过不少并发场景后觉得比较稳妥的一个值太高了数据库服务器受不了太低了高峰时不够用。connection-timeout是获取连接的最大等待时间单位是毫秒30 秒足够覆盖数据库偶尔的停顿idle-timeout是空闲连接被回收前能存活的时间。初学者在配置连接池时常犯一个错误把maximum-pool-size设成 100 甚至更大觉得连接越多越快。这是错误的因为每个连接背后都是 MySQL 服务器上的一个线程MySQL 默认最大连接数大约是 151你这边设太大一旦有并发请求数据库自己先扛不住拒绝了。本地开发就没必要动这些参数保持默认即可但到了生产环境连接池参数是要根据压测结果来调整的不是拍脑袋设的。3.3 数据库脚本与数据准备数据库连接好之后还得有一张能查的表。项目里我会提供一份init.sql脚本它的内容是在 test_db 库里建立一个user表里面放了id、name、email三个字段然后插入几条测试数据。脚本内容大致如下CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE test_db; CREATE TABLE IF NOT EXISTS user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO user (name, email) VALUES (张三, zhangsanexample.com); INSERT INTO user (name, email) VALUES (李四, lisiexample.com); INSERT INTO user (name, email) VALUES (王五, wangwuexample.com);如果你更习惯用图形化工具而不熟悉命令行可以用 Navicat 或者 IDEA 自带的 Database 插件来执行这份脚本。IDEA 右侧栏有个 Database 标签点开后新建数据源选择 MySQL填上同样的主机、端口、账号密码点 Test Connection 成功后再打开一个查询控制台把脚本复制进去执行就行。IDEA Database 工具会自动下载你选的驱动但要注意选 8.0 版本的驱动来匹配数据库版本。图省事的话也可以在 DOS 命令行里执行mysql -u root -p init.sql导入效果是一样的。我测试时的三种导入途径都跑通过你完全可以按自己的喜好来。4. 核心代码实体、数据访问层、控制器三层直连现在到了整个项目最核心也最实际的部分。我前面铺垫了那么多环境配置和连接原理最终都是为了让这几段代码能真正跑起来。为了让你看清 JDBC 的开发模式我没有采用 MyBatis 那种把 SQL 写在 XML 里的方式也没有用特别花哨的技术而是用 Spring 封装的JdbcTemplate来操作数据库既保留了 SQL 的灵活控制力又省掉了传统 JDBC 里try-catch-finally反复关闭连接的痛苦样板代码。4.1 实体类与 Repository 数据访问对象先创建一个实体类User.java来映射数据库表结构。数据库表的字段是id、name、email那么实体类的属性就跟它一一对应。这个类就是个简单的 POJO包含三个私有属性、对应的 getter 和 setter 方法以及一个无参构造器。为了调试方便我还重写了toString方法。package com.example.demo.entity; public class User { private Long id; private String name; private String email; public User() { } public User(Long id, String name, String email) { this.id id; this.name name; this.email email; } public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } Override public String toString() { return User{ id id , name name \ , email email \ }; } }接着创建UserRepository.java这是数据访问层SpringBoot 里管这个层叫 Repository 也好、DAO 也好本质上都一样。它做的事情是使用JdbcTemplate执行 SQL 并把结果集映射回实体对象。我不建议在 Controller 里直接写 JdbcTemplate 的操作代码因为明明是一个仓库类的职责写在 Controller 里会导致业务逻辑和数据库访问逻辑混在一起后续改表结构或者换数据源时改动面会大很多。package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; Repository public class UserRepository { private final JdbcTemplate jdbcTemplate; Autowired public UserRepository(DataSource dataSource) { this.jdbcTemplate new JdbcTemplate(dataSource); } public ListUser findAll() { String sql SELECT id, name, email FROM user; return jdbcTemplate.query(sql, new UserRowMapper()); } public User findById(Long id) { String sql SELECT id, name, email FROM user WHERE id ?; return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id); } public int save(User user) { String sql INSERT INTO user (name, email) VALUES (?, ?); return jdbcTemplate.update(sql, user.getName(), user.getEmail()); } public int update(User user) { String sql UPDATE user SET name ?, email ? WHERE id ?; return jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getId()); } public int deleteById(Long id) { String sql DELETE FROM user WHERE id ?; return jdbcTemplate.update(sql, id); } private static class UserRowMapper implements RowMapperUser { Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user new User(); user.setId(rs.getLong(id)); user.setName(rs.getString(name)); user.setEmail(rs.getString(email)); return user; } } }这段代码里有几个值得细品的点。构造方法接收的不是JdbcTemplate而是DataSource因为JdbcTemplate本身依赖DataSource建立连接Spring 容器在启动阶段已经根据配置文件的spring.datasource信息自动创建好了DataSourceBean这里通过构造注入把它拿过来再手动包一层JdbcTemplate。RowMapper接口是 JDBC 查询结果和 Java 对象之间的翻译器mapRow方法传入的ResultSet是指向当前行的结果集游标你只需要从里面取出当前行的各列值并填充到 User 对象里。query方法的第二个参数支持可变参数对应 SQL 中的问号占位符这是防 SQL 注入的关键设计。相比之下同一个查询如果用传统 JDBC 手写你需要先Connection conn dataSource.getConnection()然后PreparedStatement pstmt conn.prepareStatement(sql)执行完再ResultSet rs pstmt.executeQuery()最后还要在 finally 里逐个关闭这些资源。一旦忘记关闭连接连接池很快就会被耗尽。我的经验是让JdbcTemplate替你管理这些资源它能保证连接最终回到池子里代码清爽风险也大幅降低。4.2 控制器接口设计与 Restful 风格映射数据访问层写完之后需要暴露 HTTP 接口供外部调用。我建了UserController.java它通过注解的方式把方法映射到特定 URL 上。这个类的逻辑非常薄只负责接收请求参数、调用 Repository、返回结果。我刻意没有在 Controller 里加任何业务判断也没有让 SQL 的痕迹出现在这里目的是让你一眼看懂请求是从哪里进来、又最终从哪里回去的。package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/users) public class UserController { private final UserRepository userRepository; Autowired public UserController(UserRepository userRepository) { this.userRepository userRepository; } GetMapping public ListUser getAllUsers() { return userRepository.findAll(); } GetMapping(/{id}) public User getUserById(PathVariable Long id) { return userRepository.findById(id); } PostMapping public String createUser(RequestBody User user) { int result userRepository.save(user); return result 0 ? 添加成功 : 添加失败; } PutMapping(/{id}) public String updateUser(PathVariable Long id, RequestBody User user) { user.setId(id); int result userRepository.update(user); return result 0 ? 修改成功 : 修改失败; } DeleteMapping(/{id}) public String deleteUser(PathVariable Long id) { int result userRepository.deleteById(id); return result 0 ? 删除成功 : 删除失败; } }这里使用的几个注解是 Spring Web 的核心我展开讲讲。RestController表示这个类的所有方法返回值都会自动以 JSON 格式写入 HTTP 响应体不需要再手动拼接 JSON 字符串。RequestMapping(/api/users)定义了这个控制器的全局前缀意味着下面所有接口的路径都以/api/users开头。GetMapping、PostMapping、PutMapping、DeleteMapping分别对应 HTTP 的 GET、POST、PUT、DELETE 方法这正好符合 Restful 风格接口的设计规范。PathVariable能把 URL 路径里的{id}部分提取出来作为方法参数比如请求/api/users/2id 就会被赋值为 2。RequestBody则负责把请求体里的 JSON 字符串反序列化成 User 对象这里要求前后端字段名保持一致如果前端传的是username而后端实体叫name那么这个字段会是 null这类问题后面要在实战中特别注意。4.3 SpringBoot 启动类与 Maven 依赖全清单最后关键的一环就是 SpringBoot 的应用启动类。IDEA 在初始化项目时已经自动生成了它长这样package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }SpringBootApplication是一个组合注解它包含了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan三个核心作用。EnableAutoConfiguration会根据你在 pom.xml 里引入的依赖自动配置 Spring 相关的 Bean比如你引入了 JDBC 依赖且配置了数据源参数它就会自动帮你创建DataSource和JdbcTemplate的 Bean你只需要在代码里注入了。ComponentScan默认扫描主类所在包及子包下所有被Component、Repository、Service注解标记的类将它们实例化并纳入容器管理所以你的 Controller 和 Repository 类要放在com.example.demo包下放错位置就会出现启动时找不到数据的依赖注入报错。Maven 的pom.xml里还必须有spring-boot-starter-jdbc和mysql-connector-java这两个坐标去掉任何一个都会在启动阶段报缺少类的错误。一个完整的依赖段落核心代码长这样dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependencyscope设置为runtime是因为驱动类只在运行时需要编译你的业务代码时用不到 MySQL 的驱动类型这样能让 IDE 在编译期验证时更聚焦。5. 从启动到验证接口测试细节与源码结构说明代码写完之后最大的悬念就是它到底能不能跑起来。SpringBoot 项目的好处是它内嵌了一个 Tomcat 服务器不需要像传统 Java Web 项目那样去额外安装 Tomcat 并部署 war 包。你在 IDEA 里直接运行DemoApplication的 main 方法控制台开始刷日志如果最后出现Tomcat started on port(s): 8080这行关键日志说明应用已经成功启动外部请求可以被接收了。5.1 控制台启动日志的关键节点判断日志是最直观的体检报告。启动过程中有两类日志需要特别关注。第一类是HikariPool-1 - Starting...和HikariPool-1 - Start completed正常情况下这两条日志出现在 Tomcat 启动之前或者同时表示数据库连接池初始化成功如果 Hikari 日志没有出现或者出现Failed to configure a DataSource: url attribute is not specified之类的错误说明配置没有被正确读到要回去检查application.yml的位置和内容配置文件必须是resources目录下的application.yml写错文件名或者放错目录 SpringBoot 都识别不到。第二类是最终端口相关日志。如果端口被占用控制台会报Port 8080 was already in use这时有两个选择一个是改配置文件里server.port的值例如改成 8081另一个是找到占用进程并结束它。Windows 下可以用netstat -ano | findstr 8080查出占用进程的 PID然后在任务管理器里结束而在 Linux 或 Mac 上则要用lsof -i:8080查 PID。开发阶段图省事就改端口最快。5.2 Postman 与浏览器验证 CRUD我建议的验证流程是先查列表再查详情因为这两个接口最简单、反馈最快。打开浏览器或者 Postman访问http://localhost:8080/api/users如果一切正常你会在页面或工具里看到 User 对象数组的 JSON 数据。这是最终检验数据库连接是否成功的核心节点能看到数据说明整条链路从驱动加载、连接创建、SQL 执行到结果映射全都走通了。查单个用户用GET请求访问http://localhost:8080/api/users/1正常会返回 id1 的这条记录。如果传了一个不存在的 idJdbcTemplate 的queryForObject方法会抛EmptyResultDataAccessExceptionSpringBoot 默认返回一个带错误信息的 500 页面那是因为我没有为这个异常做全局拦截。实际开发中这种情况建议用自定义异常处理器返回更友好的 404 提示但初学时遇到这类异常反而能帮你理解框架异常机制的触发时机。添加用户的接口值得单独操作一遍。在 Postman 里选择POST请求URL 不变进Body页签选择 JSON 格式填入如下内容{ name: 赵六, email: zhaoliuexample.com }点击发送后正常情况下会返回添加成功的字符串。然后你再发一个GET请求刷新看看赵六这条数据应该已经出现在列表的最末尾。验证完添加之后DELETE请求访问/api/users/4可以把刚才添加的数据再删掉接口逻辑都是我测试通过了的整个过程应该足够顺畅。5.3 附带源码的工程结构说明拿到手之后的工程目录结构大致是这样的我建议你对照着自己的项目排查一下有没有漏掉文件demo ├── pom.xml └── src └── main ├── java │ └── com │ └── example │ └── demo │ ├── DemoApplication.java │ ├── controller │ │ └── UserController.java │ ├── entity │ │ └── User.java │ └── repository │ └── UserRepository.java └── resources ├── application.yml └── sql └── init.sql很多人拿到源码之后习惯直接整个目录塞进 IDEA 然后点运行跑不起来就开始皱眉。实际上一份正常的 Maven 工程导入流程是File → New → Project from Existing Sources然后选择你解压出来的pom.xmlIDEA 会识别这是一个 Maven 项目自动下载依赖。导入完成后要耐心等待右下角的 Maven 依赖加载进度条走完不要急着点运行因为本地仓库可能还没有mysql-connector-java的缓存需要去中央仓库拉取这和网络情况有关系。6. 踩坑实录我实测中遇到的三个典型报错与修复链路代码本身也许只有一百行但把整个环境从零到一地搭建起来我实测过程中遇到的报错往往比写代码更消耗时间。这一部分我不会只给你答案而是把排查链路也走一遍因为换个 IP、换个密码报错现象看上去一样但根源可能完全不同学会看日志和逐层定位比死记硬背答案有用得多。6.1ClassNotFoundException: com.mysql.cj.jdbc.Driver这个报错是最常见的原因和表象都相对直接。第一次运行报这个错的时候我的第一反应是去检查pom.xml有没有引入mysql-connector-java检查发现确实引入了。那为什么还是找不到驱动我接着去查看 Maven 的依赖树发现 IDEA 的 Maven 仓库缓存里根本没有把这个驱动下载到本地更准确地说我引入的版本号和中央仓库里的某个版本号对不上导致依赖解析失败并被 IDEA 静默忽略了。这类问题的修复链路一般是这样先检查依赖坐标是否完整注意mysql:mysql-connector-java这个 groupId 是mysqlartifactId 是mysql-connector-java不要写错然后在 IDEA 右侧 Maven 工具面板点刷新按钮强制重新解析如果还不行去本地 Maven 仓库目录通常在C:\Users\你的用户名\.m2\repository\mysql\mysql-connector-java看看有没有对应的 jar 包文件大小如果只有几 KB 说明下载损坏删除整个mysql目录后重新刷新依赖。6.2Access denied for user rootlocalhost (using password: YES)这个错误一眼看过去就是密码不对或者账号不对但实际排查时的层次还是很多的。首先要确认application.yml里password字段的缩进是否正确YAML 文件对空格极其敏感password:前面必须和username:保持相同的缩进否则值会被解析成 null连接时就会出现权限拒绝。YAML 缩进错误是 Python 和 Java 开发者都容易踩的坑而且它的报错信息有时候不会直接说是配置解析失败而是断在后面的连接阶段非常误导人。其次MySQL 8.0 默认的密码加密方式是caching_sha2_password而旧版本的连接驱动或一些老的客户端工具只支持mysql_native_password。虽然新驱动兼容这个新加密方式但如果你是从别处拷贝来的老工程驱动版本停留在 5.x那就要考虑是不是加密方式不兼容导致的认证失败。解决方式有两种一是升级驱动版本到 8.x二是在 MySQL 命令行里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;来修改加密规则。这是兼容性修复算是老项目维护时很实用的手段。6.3name jdbc is not bound in this context这个报错信息看起来非常让人困惑它和数据库连接的关系不是那么直接。它的完整报错往往出现在使用 JNDI 数据源的项目或者是在传统 JSP/Servlet 容器里部署时尝试从容器上下文查找名为jdbc的数据源引用但是容器中并没有配置这个名称的数据源。SpringBoot 内嵌 Tomcat 默认不启用 JNDI因此这种错误大概率出现在把老 Spring 工程往 SpringBoot 迁移的过程中。如果你是在纯 SpringBoot 工程里看到这个报错我的排查方向是去搜整个代码库里有没有Resource(name jdbc)或者new InitialContext().lookup(java:comp/env/jdbc/xxx)这类调用。JNDI 的思路是从外部容器获取数据源而 SpringBoot 的思路是自己创建数据源两种架构完全不同。解决方式是把这种 JNDI 查找替换成 Spring 的构造注入或者直接注入DataSource。这个排查过程提醒大家一个本质问题你用的是 SpringBoot 还是老 Spring MVC它们获取数据源的方式底层逻辑是有代际差异的网上很多教程混着讲看的时候要自己分清。7. 进阶示例PreparedStatement 防注入与事务管理示例如果你已经能跑通上面的 CRUD 接口说明你对 SpringBoot 加 JDBC 的基本组合已经入门了。这一部分我会再往上走一步讲两个在真实项目中一定会碰到的话题SQL 注入防护和事务控制。这两部分能帮你把代码从“能跑”提升到“能上线”的水平。7.1 为什么用问号占位符而不是字符串拼接我前面的 Repository 代码里所有 SQL 的参数都是用?占位符传入的这是 JDBC 规范里PreparedStatement的核心用法。很多人初学时为了方便会写出字符串拼接的语句比如SELECT * FROM user WHERE name name 这种写法在功能上可能没问题但存在严重的注入风险。用户在输入框里填一个 OR 11这样的内容拼接出来的 SQL 就变成了SELECT * FROM user WHERE name OR 11条件恒为真一次就把整张表的数据全查出来了。使用?占位符后JdbcTemplate 会将传入的参数交给数据库驱动由驱动对参数进行转义和类型转换后再作为纯数据填充到 SQL 中任何试图改变 SQL 语义的内容都会被当作字符串字面量处理注入攻击自然失效。这是一种写点代码时压根不用经大脑思考就能获得的防护能力我强烈建议你从一开始就养成用占位符的习惯。7.2 JdbcTemplate 的事务控制方式JdbcTemplate每次调用update方法时如果对应表使用的存储引擎支持事务默认是自动提交的也就是说每条 SQL 执行完立即生效。但在真实业务中往往需要多步操作共进退比如转账功能涉及“扣款”和“加款”两条 SQL其中一条失败时另一条不能单独提交。SpringBoot 里控制事务有两种方式。最简洁的是在方法或类上直接加Transactional注解Spring 容器会为这个方法创建一个数据库事务边界方法内所有数据库操作共享同一个事务任何一步抛出运行时异常前面已经执行成功的操作会一起回滚。另一个手动方式是使用TransactionTemplate它更灵活可以在代码里动态决定提交还是回滚。这部分内容在你需要操作多于一张表的时候非常有用我这里只做概念性说明具体实践可以等项目需求出现了再着手。8. 分发、部署与后续扩展从本地运行到外部访问项目在本地 IDEA 里跑通了这只是第一步。很多学完基础的人下一步会想把它发到云服务器上或者打包给同事用。SpringBoot 的部署方式相比老式 Java Web 项目方便太多这里给你讲清楚两种常用的分发方式。8.1 打成可执行 Jar 包与命令行启动在 IDEA 的 Maven 工具面板里展开Lifecycle双击package这个命令Maven 就会开始编译、执行测试如果有的话、最终打包。打包完成后在项目的target目录下会生成一个名字类似demo-0.0.1-SNAPSHOT.jar的文件这个 jar 包是 SpringBoot 内置 Tomcat 的 fat jar包含了所有依赖字节码可以直接用java -jar命令启动完全不需要额外的 Tomcat 容器。把这个 jar 包上传到服务器上命令行里执行java -jar demo-0.0.1-SNAPSHOT.jar看到和 IDEA 里一样的启动日志就代表成功。这里要注意几个生产环境差异数据库连接串里的localhost要改成服务器的实际 IP防火墙要放行对应端口以及数据库的远程访问权限默认是关闭的需要给root用户授远程权限。如果你用的是 Windows 服务器可以考虑把它注册成服务如果用的是 Linux可以用nohup java -jar xxx.jar 让进程在后台运行或者用 systemd 配置成开机自启。8.2 一个打包前的常见坑打 jar 包前有一个容易忽略的问题就是如果把spring-boot-maven-plugin的配置遗漏了打出来的 jar 包执行java -jar会报“没有主清单属性”的错误。这个插件的主要作用就是把你的应用打包成一个可执行的 fat jar并在 MANIFEST 文件里写入启动类信息。Spring Initializr 初始化的项目默认在 pom.xml 里带着这个插件所以你在自己新建模块时不要把它删掉。9. 我最后的实操体会这套组合还能怎么延伸前几天重新整理这套方案的时候我翻到一年前自己第一次跑通 JDBC 连接时保存的控制台截图当时那种“终于通了”的兴奋感其实挺值得回味的。从现在的视角回看SpringBoot 加 JDBC 这套组合最适合当跳板它的意义不是让你以后一直用 JdbcTemplate 写生产代码而是让你透过这一层封装看清数据访问底层的模样驱动是什么、连接串怎么拼、结果集怎么映射、连接池怎么复用。把这个根扎稳了之后不管是切到 MyBatis、MyBatis-Plus 还是 JPA你都会发现它们只是换了一种帮你生成 SQL 和组织结果的方式背后的数据源原理都是同一个体系。如果接下来想继续深入我建议你做三个方向的小练习第一把 Repository 层的基本 CRUD 改成用命名参数的方式体验一下NamedParameterJdbcTemplate在 SQL 字段多的时候有多好用第二在 Controller 里加一个统一的全局异常处理类用RestControllerAdvice把EmptyResultDataAccessException之类的问题转换成友好的提示信息第三试着用连接池参数里的maximum-pool-size做个并发压测用 JMeter 模拟十几个线程同时查接口观察一下连接池的分配和释放过程在日志里的表现。这几个方向都会让你对当前这套方案的运转机制有更深的理解。本文还有配套的精品资源点击获取

相关新闻