MySQL+JDBC+JUnit:Java后端数据库操作与测试完整指南
干 Java 后端这行每年帮新人排查数据库问题听得最多的一句话就是“我照着教程写了怎么就是连不上”。其实从 MySQL 的安装管理到 JDBC 的操作步骤再到用 JUnit 去验证这些代码是一条完整的链路任何一个环节理解不到位后面全卡壳。这篇文章我就把这条链路从头到尾走一遍内容包括MySQL 数据库的安装与日常管理、JDBC 操作数据库的完整步骤、怎么用 JUnit 给 DAO 层写可回归的测试最后再送一个实用小技巧——怎么把压缩包里解压出来的 exe 免安装程序在桌面上放一个图标。适合谁看如果你是刚学 JavaWeb 的学生或者正在复习数据库操作的后端新人这篇文章能帮你把散落的知识点串起来。我不会讲太多理论空话尽量按照实际干活儿的方式来说每一步都直接给能跑、能用的方案。1. MySQL数据库管理先把“数据放哪里”这件事搞明白1.1 MySQL在技术栈里的位置MySQL 是市面上最常见的开源关系型数据库管理系统很多中小型项目、学习项目、甚至一些大型互联网公司内部都在用它。你可以把数据库理解成一个“数据仓库”Java 程序本身跑在内存里关了机数据就没了所以需要 MySQL 这样的人把数据持久化地存到磁盘。为什么开源的管理系统会成为默认选择原因很简单免费、生态成熟、资料多。遇到问题一搜基本都有答案这一点对新手来说比任何特性都重要。MySQL 的底层是 C/C 写的服务器对外提供 SQL 接口Java 程序通过 JDBC 驱动和它通信。记住这个分工程序负责逻辑MySQL 负责存储。1.2 装完MySQL之后的三件必做之事我见过太多人装完 MySQL 就开始写 JDBC结果第一步就死在 连接不上 上。装完之后请老老实实完成下面三件事第一确认服务已经启动。Windows 下用命令net start mysql或者去“服务”窗口里看 MySQL 服务的状态Linux 下用systemctl status mysqld或mysql看发行版检查。服务没起来后面所有操作都是白扯。第二拿到初始密码并改掉。MySQL 8.0 在初始化时会生成一个临时密码一般写在启动日志里。Windows 安装版在安装目录的my.ini同目录日志中Linux RPM 安装后通常看/var/log/mysqld.log。临时密码长这样A temporary password is generated for rootlocalhost: xxxxxxxx。拿到后用mysql -uroot -p登录立刻执行ALTER USER rootlocalhost IDENTIFIED BY 你的新密码;注意 MySQL 8 的默认密码策略要求至少8位、包含大小写字母数字和特殊符号如果就想用简单密码得先降低校验策略新手一般不建议折腾直接用强密码最省事。第三确认端口和字符集。默认端口 3306如果被占用会启动失败这也是很多人“明明装好了却连不上”的原因。用select port, character_set_database;查看当前配置字符集建议是utf8mb4这个会直接关系到中文能不能正常入库。1.3 建库建表时值得花心思的四个细节建库很简单CREATE DATABASE demo DEFAULT CHARSET utf8mb4;一行搞定。但建表时我建议你花点心思等数据量大了再回头改表成本高得吓人。我常用的建表语句长这样CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;这里有几个细节想单独强调主键用BIGINT AUTO_INCREMENT不要用业务字段当主键。等以后做关联查询、分库分表时自增整数主键最省心。字段能设 NOT NULL 就设 NOT NULL避免代码里出现一堆null判断。表名和字段名我习惯加前缀t_没什么硬性规范纯粹为了避免和 SQL 关键字冲突比如你要建一张order表order是关键字不加反引号就是报错。存储引擎用 InnoDB支持事务、支持外键、崩溃恢复能力强。MyISAM 也就老项目里还能见到新项目别碰。2. JDBC操作数据库步骤六步走通但每一步都有讲究2.1 JDBC背后的运行逻辑JDBC 是 Java 访问数据库的一套标准接口全称 Java Database Connectivity。你要记住一个关键点JDBC 本身只是接口真正干活的“实现”是数据库厂商提供的驱动 JAR 包。MySQL 的驱动叫mysql-connector-j以前老版本叫mysql-connector-java。打个比方JDBC 就像充电接口标准驱动就是充电头MySQL 是充电桩。你写的代码只面向标准接口底层换 Oracle、PostgreSQL只要驱动和连接串换掉业务代码基本不用动。这也是 JDBC 这么多年还值得学的原因——它是一切 ORM 框架MyBatis、Hibernate的地基。2.2 标准六步与关键代码不管业务多复杂JDBC 操作数据库的核心步骤我拆成了六步你按这个模板写基本不会乱第一步注册驱动。MySQL 8 之后驱动类名变成了com.mysql.cj.jdbc.Driver老教程里的com.mysql.jdbc.Driver已经过时了虽然 MySQL Connector/J 8 为了兼容还保留着老类名但控制台会打印警告。Class.forName(com.mysql.cj.jdbc.Driver);第二步获取连接。这里最容易出幺蛾子连接串必须写对。String url jdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingUTF-8allowPublicKeyRetrievaltrue; Connection conn DriverManager.getConnection(url, root, 你的密码);URL 的格式是jdbc:mysql://ip:端口/数据库名?参数。serverTimezoneAsia/Shanghai是解决时区报错的characterEncodingUTF-8是解决中文乱码的allowPublicKeyRetrievaltrue是解决 MySQL 8 的认证加密问题的三个参数新手建议直接都写上。第三步创建预编译语句对象。String sql INSERT INTO t_user(username, password, email) VALUES(?, ?, ?); PreparedStatement ps conn.prepareStatement(sql);第四步给占位符赋值并执行。ps.setString(1, admin); ps.setString(2, 123456); ps.setString(3, adminexample.com); int rows ps.executeUpdate();executeUpdate()用于 INSERT、UPDATE、DELETE返回受影响行数查询用executeQuery()返回 ResultSet。第五步处理结果集。只有 SELECT 才需要这一步。ResultSet rs ps.executeQuery(SELECT * FROM t_user); while (rs.next()) { Long id rs.getLong(id); String username rs.getString(username); }第六步释放资源。记住一个原则后开的先关。if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close();2.3 Statement、PreparedStatement与事务很多新手会问为什么不用Statement非要PreparedStatement。两个原因第一安全。Statement做字符串拼接用户输入 or 11就能把你的 SQL 变成“永远为真”的查询这就是经典的 SQL 注入。PreparedStatement用预编译 参数化查询从根本上杜绝了拼接注入。第二效率。同一条 SQL 重复执行时数据库会缓存预编译的执行计划性能更好。所以我的习惯是无论什么场景都只用PreparedStatement没有例外。再提一下 JDBC 的事务。MySQL 的 InnoDB 默认自动提交但在“先扣钱、再减库存”这种需要原子性的场景里必须手动控制conn.setAutoCommit(false); try { // 多条增删改 conn.commit(); } catch (Exception e) { conn.rollback(); } finally { // 恢复自动提交释放连接 }事务是数据库正确性的最后一道防线JDBC 层面虽然写得啰嗦但这是概念基础。以后用 Spring 的Transactional说白了底层还是这一套。3. JUnit测试给JDBC代码加上安全网3.1 JUnit5 的注解和断言写完了 DAO 方法怎么验证对不对有人说跑一遍 main 方法看看结果就行了。短期没问题可一旦代码越来越复杂你改了一个方法影响到另一个方法跑 main 是发现不了的。这时候就需要 JUnit。JUnit 是 Java 生态用得非常广泛的单元测试框架最新主流是 JUnit 5Maven 依赖用dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.2/version scopetest/scope /dependency核心概念就三个注解、断言、生命周期。Test标记一个方法为测试方法可以直接右键运行。assertEquals(期望值, 实际值)判断两个值是否相等。assertNotNull(对象)判断对象非空。assertThrows(异常类, 可执行代码)验证是否抛出了指定异常。BeforeEach/AfterEach每个测试方法执行前/后运行适合开/关资源。BeforeAll/AfterAll所有测试运行前/后运行一次需要是静态方法。3.2 数据库测试的两个实操技巧给 JDBC 代码写测试最大的问题是怎么处理真实数据库。我建议初学阶段直接连本地 MySQL 做“集成测试”不用 Mock因为你要学的是连接和操作的真实链路Mock 掉数据库反而学不到东西。实操中有两个超实用的技巧第一个技巧测试前先清理数据。在BeforeEach里先执行一条DELETE FROM t_user或删除你准备插入的特定测试数据保证每个用例都是干净环境不会出现“上一次跑的数据污染了这一次的断言”这种问题。第二个技巧用事务回滚做“无痕测试”。测试里把setAutoCommit(false)执行完断言后rollback()数据库不会留下测试脏数据。这个技巧特别适合测试插入和更新逻辑Test void testInsert() throws Exception { Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { // 执行数据库操作 // 断言结果 } finally { conn.rollback(); conn.close(); } }3.3 测试生命周期与资源释放JUnit 测试方法里最容易出问题的不是断言而是资源泄漏。每个Test方法都会开连接、执行 SQL如果不关闭连接跑几百个用例后 MySQL 的连接数会被耗光报Too many connections。所以我强烈建议把获取连接和关闭连接放到生命周期方法里而不是每个测试方法里重复写。结构大概是class UserDaoTest { private Connection conn; BeforeEach void setUp() throws Exception { conn DBUtil.getConnection(); } AfterEach void tearDown() throws Exception { if (conn ! null) conn.close(); } Test void shouldInsertSuccess() throws Exception { // 用 this.conn 执行操作并断言 } }另外提醒一句测试方法命名最好用“行为化”命名比如shouldReturnUserWhenQueryByUsername比test1、test2强一万倍。等过两个月回来看测试报告一眼就知道哪个行为坏了。4. 完整实操从建库到DAO层测试的一次性走完4.1 项目结构与依赖准备光讲零散知识点不如跑一个完整小案例。我以“用户管理”为例把前面所有内容串起来。项目结构建议这样组织entity/User.java实体类字段对应t_user表。util/DBUtil.java数据库连接工具。dao/UserDao.java封装增删改查。test/dao/UserDaoTest.javaJUnit 测试。依赖只需要两个mysql-connector-j和junit-jupiter。如果你用的是 IDEA方便的做法是创建 Maven 项目把依赖写进pom.xml。如果是在普通 Java 项目里手动导 JAR记得放到lib目录后在 Project Structure 的 Libraries 里加进去否则运行时一定会报ClassNotFoundException: com.mysql.cj.jdbc.Driver这个问题我见得太多了。4.2 DAO层的增删改查实现DBUtil 工具类静态代码块注册驱动提供一个获取连接的方法public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingUTF-8allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }UserDao 里写两个方法作示范一个是插入一个是通过用户名查询public class UserDao { public boolean insert(User user) throws SQLException { String sql INSERT INTO t_user(username, password, email) VALUES(?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); ps.setString(3, user.getEmail()); return ps.executeUpdate() 1; } } public User findByUsername(String username) throws SQLException { String sql SELECT id, username, password, email, created_at FROM t_user WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getLong(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setEmail(rs.getString(email)); return user; } return null; } } } }看到我用了try-with-resources语法吗Java 7 之后推荐这么写资源会自动关闭代码干净不少比手写finally里close()稳妥。4.3 用JUnit验证DAO方法测试类里做的事情很朴素插入一条数据然后查询它断言用户名一致再插入一条重复用户名断言抛出异常。写出来大概是class UserDaoTest { private UserDao userDao; BeforeEach void setUp() { userDao new UserDao(); } Test void testInsertAndQuery() throws Exception { User u new User(); u.setUsername(zhangsan); u.setPassword(pass123); u.setEmail(zhangsanexample.com); boolean inserted userDao.insert(u); assertTrue(inserted); User found userDao.findByUsername(zhangsan); assertNotNull(found); assertEquals(zhangsan, found.getUsername()); assertEquals(pass123, found.getPassword()); } }跑一次测试绿色通过说明从建表到 JDBC 到查询逻辑整条链路都通了。这比你在 main 里手动打印然后肉眼核对要可靠得多。以后每次改动UserDao跑一遍测试立刻知道有没有改坏。5. 常见问题排查这些报错我帮你提前踩过5.1 连接阶段报错速查表新手 90% 的问题都出在连接阶段。下面这张表是我这些年帮人看代码时总结出来的高频问题直接对着找报错信息原因解决方案ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动 JAR 没导入检查依赖或 Project Structure 里是否添加了 mysql-connector-jNo suitable driver foundURL 格式写错或驱动没注册确认 URL 以jdbc:mysql://开头Access denied for user rootlocalhost用户名或密码错误检查密码忘记密码需重置Communications link failure服务没启动、防火墙拦截查看 MySQL 服务状态确认 3306 端口开放Public Key Retrieval is not allowedMySQL 8 缓存 SHA-2 认证问题URL 加allowPublicKeyRetrievaltrueUnknown database demo数据库不存在或名字写错先执行CREATE DATABASE demo;The server time zone value is unrecognized时区未配置URL 加serverTimezoneAsia/Shanghai这些报错里有两个特别坑我想展开说。第一个是No suitable driver found新手容易以为是驱动没下载其实经常是 URL 里把jdbc:mysql://写成了jdbc:mysql:/少一层斜杠或者数据库名写错了。第二个是Communications link failure我碰过最隐晦的场景是 MySQL 装了两个版本3306 端口被旧版本占着新项目本来连的就是旧库数据结构和密码全对不上。5.2 数据和字符相关的坑连接通了数据又出幺蛾子。最常见的是中文乱码。乱码的原因通常是多层字符集不一致MySQL 数据库是utf8mb4、连接 URL 没带characterEncodingUTF-8、IDEA 文件编码又是 GBK。解决思路是把所有环节统一到 UTF-8单改一处没用。还有一类问题是Incorrect string value: \xE4\xB8\xAD...这种报错看着像乱码其实是表或字段的CHARSET不是 utf8尤其创建表时没写DEFAULT CHARSETutf8mb4。解决办法就是ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4;。5.3 环境相关的隐蔽问题最后说一个很多人忽略的点mysql-connector-j版本要和 MySQL 服务器版本大致匹配。比如 MySQL 8 服务器最好用 Connector/J 8.x而不是 5.x。虽然旧驱动有时也能连但在加密协议、认证方式上会撞出各种诡异错误看起来像乱码其实是握手协议不对。另外IDEA 里写 JUnit 测试时如果右键测试方法没有“运行”选项很可能是没在项目里正确配置 JUnit 测试库或者测试类没有放在src/test/java目录下Maven 约定。这不算代码问题但环境问题卡住人的次数真不比代码问题少。6. 小技巧压缩包里的exe程序怎么在桌面放一个图标6.1 免安装程序创建桌面快捷方式最后分享一个和数据库无关、但在实际工作中经常会用到的小技巧从压缩包里解压出来的免安装 exe 程序怎么让它出现在桌面。先说一个最常见的错误操作直接在压缩包内部双击 exe或者把压缩包里单个 exe 文件直接拖到桌面运行。很多程序不是单文件的它依赖压缩包里的 DLL、配置文件、资源文件单独把 exe 拖出来就会闪退或者报“缺少 dll”。正确做法是第一步把整个压缩包完整解压到固定目录。目录最好不含中文和空格路径纯英文最稳比如D:\Tools\MyApp。固定目录很重要有些免安装程序使用相对路径读取配置你移动了目录它反而跑不起来。第二步在解压目录里找到主程序 exe右键点击它选择“发送到” - “桌面快捷方式”。第三步桌面就会自动出现一个快捷方式双击即可打开。如果你的右键菜单里“发送到”没有桌面快捷方式选项Win11 有些右键精简版会隐藏也可以用笨办法右键 exe - “创建快捷方式”然后把这个快捷方式文件剪切到桌面上。效果一样只是多一步操作。6.2 让快捷方式更好用的三个小优化快捷方式是生成了但默认名字通常是“xxx.exe - 快捷方式”图标也未必好看。我习惯再做三个小优化这几招在很多同事机器上都用过反馈都还不错。第一改名。桌面右键快捷方式 - 重命名把“ - 快捷方式”后缀删掉。Windows 不会因为你删掉这几个字就让快捷方式失效放心改。第二改图标。有些 exe 自带资源里的图标很丑或者干脆是空白图标。右键快捷方式 - 属性 - “快捷方式”选项卡 - “更改图标”可以浏览到可执行文件同目录下的.ico图标文件或者直接从 exe 自带的图标组里选。第三如果这个程序需要管理员权限运行比如它要读写系统目录、修改注册表右键快捷方式 - 属性 - “兼容性”选项卡 - 勾选“以管理员身份运行此程序”。不勾的话双击可能会弹出 UAC 或者是无声失败。如果你用的是 Windows 10 或 11还有一个更快的路径选中快捷方式后按Win Shift 方向键可以把它固定到其他显示器但更常用的是右键快捷方式 - “显示更多选项” - “固定到任务栏”。固定到任务栏之后桌面和任务栏各留一份随用随点特别适合那些不写桌面图标的绿色软件。至于“从压缩包中”这个前提我再多提醒一句如果你是在压缩包里右键 exe 创建的快捷方式解压之后请删掉重建因为压缩包内的虚拟路径和解压后的真实路径不一致双击很可能会提示找不到文件。这也是很多人反应“快捷方式没用”的根本原因。顺便延伸一下这种免安装程序和安装版程序的区别在于安装版会帮你在“开始菜单”、注册表、桌面快捷方式里全部处理妥当而免安装版一切都得手动来。理解了这个逻辑以后遇到任何绿色软件都知道怎么整理桌面了。最后再分享一点个人体会数据库相关的东西报错信息永远是最好的老师。我见过太多人一看到英文报错就慌其实慢慢把报错读完80% 的问题自己就能定位剩下的就是表格里那点事。JDBC 和 JUnit 这两样东西刚开始觉得繁琐一旦你把连接逻辑、测试套路固定成自己的模板后面写业务代码会很顺手。把环境配置好、把测试跑起来、把常见错误记住这三件事做扎实比背多少面试题都管用。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →