ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL JDBC JUnit实战:Java后端数据存储到单元测试全解析

MySQL JDBC JUnit实战:Java后端数据存储到单元测试全解析 MySQL、JDBC、JUnit这三个词放在一起基本就是Java后端新手绕不开的铁三角MySQL负责把数据老老实实存下来JDBC负责让Java代码能读写这些数据JUnit则在背后盯着你的CRUD代码别搞出幺蛾子。这篇我打算把这三块一次性讲透顺带解决一个挺实用的小问题——怎么把压缩包里解压出来的exe程序加到桌面快捷方式省得每次都要钻进安装目录里翻。适合正在学JavaWeb、准备写课设、或者刚接手老项目维护的朋友看完可以直接照着操作不用再东拼西凑查零散资料。先说点实际的这次我尽量不摆教科书脸全程用我实际跑过的命令、踩过的坑、写过的代码来讲。你会发现数据库管理、JDBC编程、单元测试这三件事其实是环环相扣的一条链前面每一环都是为了让你后面写代码的时候少一点痛苦。1. MySQL选型与安装先把存储这层地基打牢很多人一上来就急着写JDBC代码结果连接串都写不对报错信息看半天也不知道是数据库没起来还是驱动没加载。所以咱们先花点时间把MySQL本身搞明白这是所有操作的前提。1.1 为什么是MySQL而不是别的数据库数据库这块可选的产品很多但MySQL能成为几乎所有Java后端项目的默认选项不是没有道理的。首先它开源免费社区版完全够用对企业来说没有授权费压力。其次它的生态实在太成熟了从安装教程、面试题到各种坑的解决方案搜索引擎一抓一大把遇到问题基本都能找到答案。跟Oracle、SQL Server比MySQL的部署要轻量得多。一台普通机器、几百MB的安装包就能跑起来不像某些商业数据库动辄几个GB还一堆License限制。跟PostgreSQL比MySQL胜在使用门槛低、上手快尤其是配合Java生态各种ORM框架、连接池、中间件对MySQL的支持都是第一优先级的。我在实际项目里见过很多老系统直接用MySQL当业务主库配合Redis做缓存、ES做搜索这种组合在中小规模系统里非常耐打。选型的时候不必纠结“最强”还是“最先进”够用、稳定、团队熟悉往往比技术特性更重要。1.2 从零装好MySQLWindows和Linux两种姿势官方下载地址就是mysql官网进去找Download按钮选MySQL Community Server别走神去点了企业版。Windows下推荐用MSI安装包省心Linux下如果机器没网或者不想用yum直接下载rpm包离线装。Windows安装有几个容易忽略的点安装类型选Server only就够了除非你需要开发调试组件端口默认3306基本不用改但要注意别被占用加密规则那块8.0默认是caching_sha2_password如果你要用Navicat这类老客户端连接连接报错的话就得考虑改成mysql_native_password。装完后服务默认是自动启动的检查一下Windows服务里有没有MySQL这个条目就行。Linux离线安装rpm是另一种常见场景我之前给内网服务器装就用的这招。先准备好rpm包然后按顺序执行# 先查一下系统是否已经装了mysql或mariadb rpm -qa | grep -i mysql rpm -qa | grep -i mariadb # 如果有mariadb先卸载否则会冲突 rpm -e --nodeps mariadb-libs # 按依赖顺序安装rpm包 rpm -ivh mysql-community-common-*.rpm rpm -ivh mysql-community-client-plugins-*.rpm rpm -ivh mysql-community-libs-*.rpm rpm -ivh mysql-community-client-*.rpm rpm -ivh mysql-community-server-*.rpm # 初始化数据目录 mysqld --initialize --usermysql # 启动服务 systemctl start mysqld systemctl enable mysqld这里有个关键细节初始化之后系统会生成一个临时密码在日志文件里。你要用这个临时密码才能第一次登录grep temporary password /var/log/mysqld.log mysql -uroot -p进去之后第一件事就是改密码。MySQL 8.0的密码策略默认比较严格至少8位而且要包含大小写字母、数字和特殊字符。如果只想测试用可以调低validate_password的强度但这只建议本地开发环境干这事生产环境别偷懒。还有个小坑CentOS或银河麒麟这类系统上启动MySQL有时会遇到权限不对导致起不来。检查一下数据目录和日志文件的属主是不是mysql用户不是的话chown -R mysql:mysql改回来。遇到“net start mysql 服务无法启动”这类问题不要慌先看错误日志百分之八十是以下三件事3306端口被占、data目录权限不对、初始化没完成。1.3 库、表、存储引擎与事务核心概念串讲装好MySQL之后咱们得聊清楚它到底怎么管理数据。MySQL的逻辑结构分三层实例服务进程、数据库Database、表Table。一个实例可以建多个库一个库里有多个表表是真正存数据的地方。先看几个最常用的管理命令-- 查看所有数据库 SHOW DATABASES; -- 创建数据库指定字符集 CREATE DATABASE demo_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 使用数据库 USE demo_db; -- 创建表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, age INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;字符集这块建议无脑用utf8mb4因为MySQL老版本默认的utf8是utf8mb3不支持四字节的emoji字符写进去会报错。utf8mb4是完整的UTF-8实现跟“设置默认值为0”这类细节一样看起来小但实际运行时能帮你挡掉一堆乱码问题。存储引擎是MySQL设计里很特别的一环。InnoDB支持事务、行级锁、外键崩溃恢复能力也强是现在绝对的主流选择。MyISAM是另一个老牌引擎读多写少、全文索引有历史优势但它不支持事务表锁容易在并发写入时卡死。所以新表一律用InnoDB不要犹豫。如果遇到有人问MySQL怎么创建索引本质上就是把常用查询条件字段建上索引来加速CREATE INDEX idx_username ON user(username);事务是InnoDB才有的能力核心就是ACID四个特性——原子性、一致性、隔离性、持久性。打个比方转账这件事A扣钱和B加钱必须同时成功或者同时失败绝不能出现A扣了但B没到账。后面讲JDBC的时候你会看到Java代码里怎么控制提交和回滚底层就是在配合MySQL的事务机制。2. JDBC操作数据库从驱动加载到资源关闭的六步走数据库装好了接下来就到了Java程序怎么跟它对话的问题。JDBCJava Database Connectivity就是Java官方的数据库访问规范它定义了一组接口然后各个数据库厂商提供对应的实现。这个设计放到今天看依然很优雅你写的代码只依赖JDBC接口换数据库厂商只需要换驱动jar包代码本身基本不用动。2.1 先弄懂JDBC的四个核心角色JDBC这套体系里有四个核心角色搞懂它们后面一切代码都好理解。DriverManager是驱动管理器负责加载驱动并建立物理连接你可以把它理解成一个“接线员”。Connection是数据库连接代表一条网络会话通道它管着后面所有SQL的执行上下文。Statement是SQL语句的载体负责把SQL送到数据库执行你可以理解为“快递员”。ResultSet是查询结果集相当于一张虚拟表迭代它就能拿到每一行数据。这四者的关系用一句话串起来DriverManager接线员拉通Connection通道Statement把SQL快递进去数据库执行完把结果放进ResultSet里让你慢慢翻。就是这么回事JDBC编程本质是四者协作的过程。2.2 JDBC的标准六步操作理论讲再多不如直接上代码这六步是我写了无数次后总结的固定套路从加载驱动到关闭资源每一步都有它存在的理由。import java.sql.*; public class JdbcDemo { public static void main(String[] args) { Connection conn null; PreparedStatement pstmt null; ResultSet rs null; try { // 1. 注册驱动 Class.forName(com.mysql.cj.jdbc.Driver); // 2. 获取连接 String url jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruecharacterEncodingutf8; String user root; String password your_password; conn DriverManager.getConnection(url, user, password); // 3. 创建PreparedStatement预编译SQL String sql INSERT INTO user(username, age) VALUES(?, ?); pstmt conn.prepareStatement(sql); pstmt.setString(1, 张三); pstmt.setInt(2, 25); // 4. 执行SQL int rows pstmt.executeUpdate(); System.out.println(影响行数 rows); // 5. 如果是查询处理ResultSet String querySql SELECT id, username, age FROM user WHERE age ?; PreparedStatement queryPstmt conn.prepareStatement(querySql); queryPstmt.setInt(1, 20); rs queryPstmt.executeQuery(); while (rs.next()) { int id rs.getInt(id); String name rs.getString(username); int age rs.getInt(age); System.out.println(用户 id - name - age); } } catch (ClassNotFoundException e) { System.err.println(找不到驱动类 e.getMessage()); } catch (SQLException e) { System.err.println(SQL执行异常 e.getMessage()); } finally { // 6. 关闭资源顺序是ResultSet - Statement - Connection try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (pstmt ! null) pstmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } } }第1步注册驱动在MySQL 8.0的驱动里Class.forName其实可以不写了因为DriverManager会自动发现。但写上有个好处能明确告诉看代码的人底层驱动是谁老项目里还经常见到老版本的com.mysql.jdbc.Driver注意区别——多一个cj是MySQL 8.0驱动的新写法。第2步的URL是最容易出问题的地方。jdbc:mysql://是协议头localhost:3306是数据库地址和端口demo_db是库名。后面的一串参数不要删useSSLfalse是为了避免SSL握手报错serverTimezoneAsia/Shanghai是告诉驱动用哪个时区不然时间字段会偏差8小时。allowPublicKeyRetrievaltrue是配合8.0的SHA256密码认证用的不加它有时候会报Public Key Retrieval is not allowed的错。这些参数都是实践中一个个踩坑换来的经验。第6步关闭资源为什么这么啰嗦因为数据库连接是稀缺资源不关的话服务器连接数会被耗尽后续请求全部排队卡死。JDK 7开始有了try-with-resources语法代码可以简洁很多但是理解底层关闭顺序仍然很重要尤其是排查连接泄漏的时候。2.3 为什么我坚持用PreparedStatement而不是Statement早期很多人学JDBC的时候写过这样的代码字符串拼接SQL用Statement执行。比如String sql SELECT * FROM user WHERE username name ;这种做法在任意一个初学项目里都能跑但是隐患极大。假设name传进来的是 OR 11拼接完的SQL就变成了SELECT * FROM user WHERE username OR 11条件恒为真整个表的记录全给你返回了这就是经典的SQL注入。PreparedStatement的占位符机制能直接把这个问题干掉——参数不走拼接而是走预编译通道数据库把SQL结构先固定住后传的参数只是纯值即便里面带了引号也只会被当作普通字符串处理。所以任何涉及用户输入的SQL一律用PreparedStatement这不是建议是底线。还有一点很多人不知道PreparedStatement对于重复执行的SQL有预编译缓存性能比Statement高。你用同一套SQL模板只是参数不同反复执行时数据库不必每次重新解析SQL结构直接换参数跑就行。业务系统里最常见的“循环批量插入”用PreparedStatement加批量执行接口会快一个量级pstmt conn.prepareStatement(INSERT INTO user(username, age) VALUES(?, ?)); for (int i 0; i 10000; i) { pstmt.setString(1, 用户 i); pstmt.setInt(2, 20 (i % 30)); pstmt.addBatch(); // 攒一批 if (i % 1000 0) { pstmt.executeBatch(); // 每1000条提交一次 } } pstmt.executeBatch();JDBC里面处理日期也要留个心眼Java的java.util.Date和数据库的DATETIME/DATE类型不是直接互通的需要转成java.sql.Date或java.sql.Timestamp或者直接用setObject配合LocalDateTime现在的高版本驱动都支持LocalDateTime参数。2.4 事务控制转账场景现场演示JDBC默认是自动提交模式每个SQL执行完就立刻提交这在某些场景下是灾难。比如银行转账扣A账户1000给B账户加1000如果第一步成功、第二步报错那钱就凭空消失了。正确做法是把两步操作包在同一个事务里要么全成功要么全回滚。conn.setAutoCommit(false); try { String sqlA UPDATE account SET balance balance - ? WHERE id ?; PreparedStatement psA conn.prepareStatement(sqlA); psA.setBigDecimal(1, new BigDecimal(1000)); psA.setInt(2, 1); psA.executeUpdate(); String sqlB UPDATE account SET balance balance ? WHERE id ?; PreparedStatement psB conn.prepareStatement(sqlB); psB.setBigDecimal(1, new BigDecimal(1000)); psB.setInt(2, 2); psB.executeUpdate(); conn.commit(); // 全部成功提交 } catch (SQLException e) { conn.rollback(); // 任一失败回滚 throw e; } finally { conn.setAutoCommit(true); // 恢复自动提交 }注意事务的边界要尽量短。事务里别做耗时的外部调用更别把sleep放在事务里因为事务会持有行锁拖得越久其他会话的阻塞越严重。高并发场景下大事务和长事务是数据库性能的头号杀手之一MySQL的information_schema.INNODB_TRX表里可以直接查到正在跑的事务排查死锁和长时间未提交的问题全靠它。如果你到了生产环境Java代码里一般不会直接这么裸写JDBC事务而是用Spring的Transactional但底层机理还是这一套你把原生的搞明白了注解就是一层皮而已。3. JUnit给数据库代码上保险再也不用main方法戳很多初学者测试数据库代码的方法简单粗暴写个main方法里面new一个DAO对象CtrlShiftF10跑一下拿眼睛盯着控制台输出判断对不对。但这种方式有个致命问题没有留痕没有反馈改造代码之后也不知道有没有把老功能弄坏。JUnit就是来解决这个痛点的它是Java事实上的单元测试标准让你用“断言”代替“眼睛看”用“自动化回归”代替“手工重跑”。3.1 为什么数据库层代码更需要JUnit数据库层的代码最怕改出一个隐蔽问题今天加了一个字段明天调了一下SQL后天换了个连接池每一项改完你都得确认“旧的还在跑”。手工验证一两次还能忍次数多了必翻车。JUnit能把验证固化成一个可以随时一键执行的用例集改完代码点一下绿条全绿说明没破坏红了会告诉你哪个断言挂了、期望值和实际值差多少。另一个容易被忽视的价值JUnit能明确呈现数据关系的测试意图。比如测试“插入用户之后能查到该用户”这个用例一旦写出来它就在告诉后来接手的人——这个DAO方法的行为契约是什么。比起翻几百行业务代码去猜几个命名清晰的测试方法要直观得多。3.2 JUnit 5的核心注解速查JUnit 5是目前的主力版本核心注解并不多掌握这几个就能覆盖绝大多数场景注解作用使用频率Test标记一个方法为测试用例每个用例必写BeforeEach每个测试用例执行前运行初始化连接和准备数据AfterEach每个测试用例执行后运行清理数据、关闭资源BeforeAll所有用例执行前运行一次只创建一次连接池AfterAll所有用例执行后运行一次销毁连接池DisplayName给测试方法起中文名强烈建议加Disabled跳过某个测试临时屏蔽用例配合Assertions类里的断言方法测试就真正具备了“自我判定”能力。断言才是JUnit的精髓——assertEquals(expected, actual)会对比期望值和实际值不一致就抛异常、用例标红assertTrue(condition)验证条件必须成立assertNotNull(obj)确保对象不能为空。这一套比用System.out.println靠谱一百倍因为断言失败是有明确信号的而控制台输出不仔细看根本不知道对没对。3.3 一个完整的DAO层测试实战下面用JUnit 5写一个连接真实MySQL的测试类测试前面JDBC那节里的用户插入和查询操作。重点是看事务回滚的用法——测试完数据要还原不要污染数据库。import org.junit.jupiter.api.*; import java.sql.*; import static org.junit.jupiter.api.Assertions.*; class UserDaoTest { private static Connection conn; BeforeAll static void initConnection() throws SQLException { String url jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai; conn DriverManager.getConnection(url, root, your_password); } AfterAll static void closeConnection() throws SQLException { if (conn ! null !conn.isClosed()) { conn.close(); } } BeforeEach void setUp() throws SQLException { conn.setAutoCommit(false); // 每个用例前开启事务 } AfterEach void tearDown() throws SQLException { conn.rollback(); // 每个用例后回滚保证数据不被污染 conn.setAutoCommit(true); } Test DisplayName(插入用户后能够查询到该用户) void testInsertAndQuery() throws SQLException { // 准备数据 PreparedStatement insertPs conn.prepareStatement( INSERT INTO user(username, age) VALUES(?, ?)); insertPs.setString(1, JUnit测试用户); insertPs.setInt(2, 30); int rows insertPs.executeUpdate(); assertEquals(1, rows, 插入应当影响1行); // 查询数据 PreparedStatement queryPs conn.prepareStatement( SELECT id, username, age FROM user WHERE username ?); queryPs.setString(1, JUnit测试用户); ResultSet rs queryPs.executeQuery(); assertTrue(rs.next(), 应当能查到插入的用户); assertEquals(JUnit测试用户, rs.getString(username)); assertEquals(30, rs.getInt(age)); rs.close(); } Test DisplayName(更新用户年龄后数据同步) void testUpdate() throws SQLException { // 先插入一条 PreparedStatement insertPs conn.prepareStatement( INSERT INTO user(username, age) VALUES(更新用户, 20)); insertPs.executeUpdate(); // 更新年龄 PreparedStatement updatePs conn.prepareStatement( UPDATE user SET age ? WHERE username ?); updatePs.setInt(1, 28); updatePs.setString(2, 更新用户); int updated updatePs.executeUpdate(); assertEquals(1, updated); } }这套测试里最关键的是BeforeEach开事务、AfterEach回滚这两个方法配合起来你的测试用例想跑多少遍都行数据库始终保持在初始状态。不做回滚的话每次跑测试都会往表里塞数据跑个几十遍表就花了。这是数据库单元测试从新手到老手最重要的一道分水岭。当然JUnit测试也分级别。像上面这种连接了真实MySQL的本质上是集成测试适合在测试环境跑纯净的单元测试则会用Mock工具隔离外部依赖。但以咱们这篇的定位能自己控制事务回滚、连真库做验证已经比一堆人强了。需要同步给团队做回归的话后续还可以接上Maven的surefire插件让mvn test一键跑全部用例。4. 从压缩包到桌面给exe程序建快捷方式那些事标题里还有一个挺生活化的问题很多人下载的绿色软件是压缩包形式解压之后是一堆文件只有里面的exe才是真正的主程序。每次用都得钻进解压目录去双击别提多麻烦。建快捷方式这事说难不难但有几个坑确实值得念叨念叨。4.1 Windows 11 / 10下最省事的操作先说最常规的办法解压完软件后进入解压目录找到主程序exe一般是文件名最像软件名、图标最显眼、体积通常也最大的那个右键它选择“发送到” - “桌面快捷方式”。完事。桌面上就会出现一个同名快捷方式以后双击就能启动。如果想把图标和名字收拾得更顺眼一点右键桌面的快捷方式选择“属性”在“快捷方式”选项卡里可以改目标路径、起始位置和图标。一些绿色软件自带多个图标资源你可以在“更改图标”里浏览到exe文件本身或同目录下的ico文件选一个你喜欢的。这一步纯看个人喜好不影响功能。这里面最大的坑是解压之后整个软件文件夹不能随便挪位置。绿色软件不像安装版会在系统里注册路径它依赖的是“自己所在目录”下的文件和配置。你把文件夹从D盘挪到E盘桌面快捷方式的目标路径还指向旧的D盘地址一双击就报“找不到应用程序”。解决办法也简单——挪完文件夹重新右键exe再发一次快捷方式。4.2 不能直接创建快捷方式的异常情况有个细节经常把新手卡住有的压缩包解压出来发现根本没有exe只有一个start.bat或者.vbs脚本。这种情况通常说明它是带环境依赖的启动器比如需要先设置JAVA_HOME、再调用jar包之类的。这时候你就不能直接对bat建快捷方式了其实也可以右键bat发送到桌面一样能快捷启动。但要注意bat执行时窗口可能会一闪而过如果启动失败右键快捷方式选“以管理员身份运行”或者先手动打开cmd跑一遍bat看报错信息。还有一种情况exe本身是某个安装引导程序真正的主程序在子目录里。装完才发现桌面上自己会生成图标这种就属于安装版软件不太算绿色软件了不用我们操心。判断标准很简单把文件夹单独拷到别的电脑上还能跑就是绿色版不行就是没戏。4.3 顺带说一嘴Linux下的.desktop快捷方式如果你用的是Linux桌面环境比如银河麒麟、Ubuntu做法就不是右键发送了而是要手动写一个.desktop文件放到~/.Desktop或者/usr/share/applications里。模板长这样[Desktop Entry] NameMyApp Exec/home/user/soft/myapp/myapp Icon/home/user/soft/myapp/myapp.png TypeApplication Terminalfalse CategoriesDevelopment;写完记得执行chmod x myapp.desktop否则系统不认。这个属于题外话但思路跟Windows一样——桌面图标本质上就是一个“指向真实程序的入口”理解了这一点不管系统怎么变都是同一套逻辑。5. 高频问题与排查速查表最后必须把实际运行中最高频的一批问题整理出来这些都是我一次次在社区里看到、自己也踩过的坑。用表格列出来哪一天你遇到类似报错直接对照排查就行。问题现象常见原因解决方案MySQL连接报SSLConnectionErrorMySQL 8.0默认开启SSL客户端不支持或证书校验失败URL加useSSLfalse或配正确证书net start mysql服务无法启动3306端口被占用data目录权限不对初始化未完成检查端口netstat -ano查看错误日志重新mysqld --initialize登录报Access denied for user账号密码错误或host限制检查密码或GRANT ALL ON *.* TO root%授权忘记root密码密码遗忘修改配置文件添加skip-grant-tables重启免密登录后改密码Navicat连接MySQL 8.0失败加密规则caching_sha2_password不兼容改用mysql_native_password或升级Navicat版本JDBC驱动Maven下载失败网络原因或中央仓库连接不稳定配置阿里云镜像仓库重新导入Public Key Retrieval is not allowedMySQL 8.0的SHA256密码认证必要参数缺失URL加allowPublicKeyRetrievaltrue插入中文数据乱码数据库、表、连接三处字符集不一致全部统一用utf8mb4JDBC连接超时网络不通防火墙拦截3306端口测试telnet IP 3306放行端口MySQL性能变慢查询卡顿缺索引、慢SQL、连接数打满EXPLAIN分析执行计划创建索引检查连接数这个表格里最值得展开说明的是连接池问题。很多初学者写的JDBC代码是每次请求都新建连接、用完就关这在并发量一大时必崩。生产系统一定要用连接池HikariCP、Druid连接池的本质是提前建好一批物理连接放在池子里谁要用就从池里借用完归还而不是扔回数据库。HikariCP在Spring Boot里是默认选型它之所以快核心就是连接生命周期管理和极短的获取连接路径。你如果项目里碰见连接不够用、频繁Timeout先检查有没有用连接池再检查连接池的最大连接数配置是否合理。另外一个很容易被忽略的点是JDBC驱动版本跟MySQL服务器版本尽量保持匹配。MySQL 8.0的服务器最好搭配mysql-connector-j 8.x的驱动老版本驱动连新版本服务器偶尔会有协议不兼容的问题。驱动jar包的引入现在基本都是Maven或Gradle自动管理了但依然有人会手动往lib目录塞jar包塞之前一定看清版本号。关于Mysql顺手还要提一句存储过程和函数。业务场景中有些复杂的多步骤SQL逻辑与其用Java代码在应用层来回查好几次不如直接写在存储过程里一次调用搞定。但存储过程也不是银弹它把业务逻辑搬到了DB层不好调试、不好版本管理孟繁非繁。我个人建议核心交易链路尽量放在应用层代码里用事务控制存储过程只适合那些数据库内数据加工、定时任务类场景。再说回JUnit很多老项目压根没有任何测试代码接手的人也不敢补测试因为补完跑红了不知道怎么改。我的建议是从DAO层开始补DAO层的输入输出最清晰一封数据库连接配上断言就能写。先把最核心的增删改查测了再慢慢往上覆盖Service层这是一个成本最低、见效最快的路径。最后再分享一个个人习惯本机开发的时候我习惯把MySQL的数据目录单独挂到非系统盘日志也单独开一个文件。这样系统重装不会牵连数据排查问题时日志也好找。数据库是软件系统的地基地基不牢上面再漂亮的代码都是空中楼阁。你能把MySQL管明白、JDBC写顺手、JUnit用起来这三板斧使好Java后端开发这条路的基本盘就算真正稳了。
返回列表