
简介基于Eclipse与MySQL构建的Java超市管理系统面向需要学习Java Web开发或快速搭建进销存项目的开发者覆盖商品销售、商品采购、供应商管理、库存统计以及管理员/收银员双角色权限等核心业务模块。资源包内共378个文件包含49个Java源码、56个JSP页面、98个编译后的class文件以及SQL数据库脚本、CSS/JS前端样式脚本、PNG图片素材等其中Java与JSP对应业务逻辑和页面展示SQL用于初始化数据库压缩包大小约10.37MB目录结构清晰便于整体对照学习。目前已有2384人学习下载适合作为课程设计或毕业设计的参考项目。通过本包可获得完整可运行的项目工程、数据库初始化脚本及前端界面资源帮助理解Eclipse环境下Java Web项目从数据表设计到权限分层的实现思路同时可依据现有代码快速扩展会员管理、促销策略等功能模块。1. Java超市管理系统EclipseMySQL课设烂大街但最容易被低估的练手项目如果只看标题java超市管理系统eclipseMySQL大概是所有Java初学者最眼熟的项目——课设、毕设、培训班结业项目都是它。但我的看法恰恰相反正因为看着简单大多数人把它写成了能用就行的CRUD填表丢掉了这个题目真正值钱的部分库存扣减与订单事务的一致性、多窗口UI下的状态同步、MySQL在Windows上的环境坑以及JDBC驱动版本兼容问题。这套系统适合两类人一是准备把Java语法真正落地的初学者二是想在简历上写一个有血有肉的进销存业务系统的人。接下来的内容围绕EclipseMySQLJDBC纯Java GUI跑通一套超市进销存能复现、可改关键坑都标出来。2. 架构先行为什么这套组合能扛住以及你的环境该怎么配2.1 选型逻辑课设程序为什么敢用Swing而不是Spring Boot第一眼看到java超市管理系统eclipseMySQL很多人第一反应是这年头谁还用Swing。但实际上正因为这个项目要处理库存扣减、订单事务和多窗口联动它才是最合适的练手场景你用Spring Boot写数据库表一建接口一写前端套个模板CRUD就完事了底层JDBC怎么管理连接、事务提交失败的补偿怎么做你依然没练到。而用Eclipse的Swing/JFrame做界面用原生JDBC连MySQL每一步都暴露在你能看见的地方。我一般把这类系统的分层定成实体类entity→ 数据访问层DAO→ 业务服务层Service→ 视图层UI。实体类就是一张表的映射DAO只管SQL和ResultSet的转换Service负责业务规则比如下单时先查库存再扣减UI层只调Service不直接碰Connection。这样做的原因是如果作业要求加一个会员积分抵现你只需要在Service层改一个方法UI和DAO都不动。真到需要把Swing换成Web或JavaFX时Service层原封不动带走。当然我不是说Spring Boot不好而是说技术选型要匹配目标。这个题目的目标是把Java基础语法、面向对象、JDBC、SQL变成一套能跑通的系统SwingJDBC是成本最低、信息量最大的路径。如果你已经有Spring Boot经验那这套代码的架构价值会打折扣但事务一致性和并发扣库存这两个话题在哪种框架里都躲不开。2.2 Eclipse安装与环境匹配JDK、MySQL与驱动jar三方跳对才不玄学环境永远是第一个大坑。我的建议组合是JDK 8 Eclipse IDE for Java Developers MySQL 8.0或5.7 对应的Connector/J驱动。选JDK 8的原因不是它最新而是搜安装eclipsemysql安装教程得到的大多数资料、你学长留下的代码、学校的评分环境大概率都是JDK 8Swing在JDK 8的表现非常稳。想试更高版本JDK也没问题但要注意Eclipse版本对应关系否则启动时就报UnsupportedClassVersionError。Eclipse本体下载的安装包是zip或exe的解压后选择工作空间workspace注意路径里不要带中文和空格不然后面导入项目容易出幺蛾子。MySQL版本是个隐藏开关。MySQL 5.7和8.0的默认认证插件不一样如果你装了MySQL 8.0却把mysql-connector-java 5.1.48的jar放进lib运行时八成报Public Key Retrieval is not allowed或Unable to load authentication plugin caching_sha2_password。对策是用MySQL 8.0就选8.0.x的驱动用MySQL 5.7就选5.1.49。装上之后在Eclipse项目根目录建一个lib文件夹把jar放进去右键Build Path → Add to Build Path别直接塞到JRE/lib/ext否则用Eclipse导出jar时会连带触发一堆权限问题。另一个环境参数值得记录JDBC URL里尽量显式设置这些参数能避免大量玄学问题。下面是我常用的MySQL 8.0连接串写法String url jdbc:mysql://localhost:3306/supermarket ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue;参数说明useUnicodetrue和characterEncodingutf8解决中文乱码serverTimezone必须设否则MySQL 8.0默认UTC写入时间会差8小时useSSLfalse是因为本地学习环境没有必要为加密浪费握手时间allowPublicKeyRetrievaltrue是针对MySQL 8.0的caching_sha2_password认证插件不加这一项会报Public Key Retrieval错误。这套参数我几乎原样用在所有本地JDBC项目里换数据库时只需要改数据库名和账号密码。2.3 表结构设计六张表怎么划分、字段怎么定数据库是这类项目的地基。设计表之前先明确业务超市管理系统至少要管三件事——商品进出进销存、前台收银销售订单、后台账户用户登录与权限。所以我把表拆成tbl_user用户表、tbl_supplier供应商表、tbl_product商品主档、tbl_member会员表、tbl_sale_order销售单头、tbl_sale_item销售明细。角色字段用字符串ADMIN/CASHIER比整数可读性好密码在课程设计里存明文可以理解如果要交正式作品至少做一次MD5/SHA-256哈希。商品价格用DECIMAL(10,2)不要用float/double浮点算钱在累计汇总时会差几分钱库存用INT并在SQL里加约束stock 0。销售单头和明细拆开是因为一个订单对应多行商品这是一对多关系明细里的unit_price一定要冗余存储商品表的价格会变化订单生成之后就不能再跟随商品表改。建库建表的SQL我习惯写成这样方便在一台干净的机器上复现CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4; USE supermarket; CREATE TABLE tbl_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT CASHIER ); CREATE TABLE tbl_supplier ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20) ); CREATE TABLE tbl_product ( id INT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(30) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, warn_stock INT NOT NULL DEFAULT 5, supplier_id INT, CONSTRAINT fk_product_supplier FOREIGN KEY (supplier_id) REFERENCES tbl_supplier(id) );说明utf8mb4比utf8更完整能存生僻字和更多字符避免入库报错warn_stock表示库存预警阈值低于这个值时后面在UI里可以标红barcode设置UNIQUE是为了收银时能用条码直接定位商品也为后续接入扫码枪留口子。外键在课设里建议留着正式生产环境反而常常去掉外键——这是两种取舍教课设作业时保留外键更容易让老师看明白关系。建完表后别忘了插入一条初始管理员账户否则登录页面永远进不去INSERT INTO tbl_user (username, password, role) VALUES (admin, admin123, ADMIN);销售单和明细的表结构也一并给出后面第三章的收银代码会用到CREATE TABLE tbl_sale_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, cashier_id INT NOT NULL, member_id INT, total_price DECIMAL(10,2) NOT NULL, sale_time DATETIME NOT NULL, CONSTRAINT fk_order_cashier FOREIGN KEY (cashier_id) REFERENCES tbl_user(id) ); CREATE TABLE tbl_sale_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL, subtotal DECIMAL(10,2) NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES tbl_sale_order(id), CONSTRAINT fk_item_product FOREIGN KEY (product_id) REFERENCES tbl_product(id) );这里注意order_no加UNIQUE能防止并发时同一单号重复入库member_id允许为空散客收银时不强制选会员。建完这些表项目的地基就稳了。3. 代码落地用三层结构把登录、商品、销售跑通3.1 DBHelper连接与事务的公共底座所有DAO都围绕一个DBHelper。连接管理的关键点是谁打开谁关闭DAO里开了Connection就必须在finally里关掉不能用完就忘否则MySQL默认连接数会被耗尽后面所有请求都排队。我提供一个最常用的写法不含连接池先跑通import java.sql.*; public class DBHelper { private static final String URL jdbc:mysql://localhost:3306/supermarket ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, Statement st, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (st ! null) st.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明static块里用Class.forName注册驱动MySQL 8.0用com.mysql.cj.jdbc.Driver如果你用的驱动是5.x这里类名要改成com.mysql.jdbc.Driver这是版本切换时最典型的踩坑点。所有DAO方法结束时都调用close按ResultSet → Statement → Connection的顺序关这样不会因为忘记关闭连接导致后续操作全部卡死。3.2 登录模块从UI到DAO的全链路登录模块看似简单但它能教给你两件事如何避免SQL注入以及如何在DAO层返回业务结果而不是裸ResultSet。SQL注入在课设里不算大风险但你用PreparedStatement而不是字符串拼接SQL是一个工程师的基本习惯。先写DAO层public User login(String username, String password) { String sql SELECT id, username, password, role FROM tbl_user WHERE username? AND password?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setRole(rs.getString(role)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }逻辑说明用try-with-resources之后Connection、PreparedStatement、ResultSet都能自动关闭不用手写finally。密码在SQL层就完成比对即使异常也不会把密码带回UI。DAO返回null表示用户名或密码错误UI层只需要判断null然后弹一个JOptionPane提示。实体类User就是id、username、password、role四个字段加getter/setter不写任何业务逻辑。登录的UI层用JFrameJTextFieldJPasswordField即可。按钮点击事件里调用dao.login(username, password)如果返回非null就根据role跳转到主界面null则提示用户名或密码错误。这个模块的代码量不大但能完整体现UI调Service、Service调DAO的链路。3.3 商品管理JTable与增删改查这个模块最容易让新手翻车因为JTable的数据不是直接放List而是通过TableModel驱动。最简单可靠的做法是自定义一个DefaultTableModel把数据填充进去。刷新表格时很多人直接把表删了重建这是错的正确做法是清空model再重新addRow。public void loadProductTable(DefaultTableModel model) { String sql SELECT id, barcode, name, price, stock, warn_stock FROM tbl_product ORDER BY id; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { model.setRowCount(0); while (rs.next()) { model.addRow(new Object[]{ rs.getInt(id), rs.getString(barcode), rs.getString(name), rs.getBigDecimal(price).setScale(2), rs.getInt(stock), rs.getInt(warn_stock) }); } } catch (SQLException e) { e.printStackTrace(); } }参数说明model.setRowCount(0)是刷新表格的标准动作清空旧数据再填充新数据否则每次刷新都会把旧行累加在底部。价格用BigDecimal取出后再setScale(2)统一格式显示上不会出现19.90变成19.9的问题。执行完这段之后在UI的按钮事件里调用loadProductTable(model)即可完成刷新。新增和修改商品时参数要按表结构来。新增的SQL是INSERT INTO tbl_product(barcode, name, price, stock, warn_stock, supplier_id) VALUES(?,?,?,?,?,?)修改是UPDATE加上WHERE id?。这里有一个需要坚持的原则删除商品不要物理DELETE而是在商品表加一个status字段1上架/0下架或者把删除逻辑限定为库存为0才能删。真实超市里商品主档是要留痕的物理删除会把历史销售明细的外键一起搞崩级联删除容易误伤。课设里如果老师没要求你可以物理删但你要知道这个做法在真实业务里是踩坑行为。3.4 销售收银一个方法里完成下单扣库存记明细这是整套系统的核心难点。它需要同时完成三件事往tbl_sale_order插入订单头往tbl_sale_item插入明细扣减tbl_product的库存。这三件事必须放在同一个数据库事务里否则会出现订单建了、库存没扣的脏数据。典型错误是拍脑袋把三句SQL用三个Connection分开执行一旦中途抛SQLException数据就半截了。我常用的写法在Service层里把Connection拿出来手动setAutoCommit(false)执行完所有语句后commit任何一步失败就rollback。下面是核心代码骨架public boolean checkout(String orderNo, int cashierId, ListCartItem items) { Connection conn null; PreparedStatement psOrder null; PreparedStatement psItem null; BigDecimal total BigDecimal.ZERO; try { conn DBHelper.getConnection(); conn.setAutoCommit(false); for (CartItem item : items) { String qtySql SELECT stock FROM tbl_product WHERE id? FOR UPDATE; try (PreparedStatement psQty conn.prepareStatement(qtySql)) { psQty.setInt(1, item.getProductId()); try (ResultSet rs psQty.executeQuery()) { if (rs.next()) { int stock rs.getInt(stock); if (stock item.getQuantity()) { throw new RuntimeException(库存不足: item.getProductName()); } } } } psItem conn.prepareStatement( UPDATE tbl_product SET stockstock-? WHERE id? AND stock?); psItem.setInt(1, item.getQuantity()); psItem.setInt(2, item.getProductId()); psItem.setInt(3, item.getQuantity()); psItem.executeUpdate(); } psOrder conn.prepareStatement( INSERT INTO tbl_sale_order(order_no, cashier_id, total_price, sale_time) VALUES(?,?,?,NOW()), Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, orderNo); psOrder.setInt(2, cashierId); psOrder.setBigDecimal(3, total); psOrder.executeUpdate(); int orderId -1; try (ResultSet keys psOrder.getGeneratedKeys()) { if (keys.next()) { orderId keys.getInt(1); } } psItem conn.prepareStatement( INSERT INTO tbl_sale_item(order_id, product_id, quantity, unit_price, subtotal) VALUES(?,?,?,?,?)); for (CartItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getProductId()); psItem.setInt(3, item.getQuantity()); psItem.setBigDecimal(4, item.getUnitPrice()); psItem.setBigDecimal(5, item.getSubtotal()); psItem.executeUpdate(); total total.add(item.getSubtotal()); } conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBHelper.close(null, psOrder, conn); } }代码里有个最容易被忽略的细节检查库存时用SELECT ... FOR UPDATE这一句在MySQL的InnoDB引擎下会锁定对应的行记录避免两个收银台同时在最后一件商品上并发超卖。这个点写进课设报告里老师会高看一眼。另外扣库存的UPDATE本身也带上AND stock?条件即使上面检查通过了这里也有一道保险返回值0就说明库存不足直接抛异常回滚。orderId通过Statement.RETURN_GENERATED_KEYS回填这是获取自增主键的教材级正解比先查max(id)1更安全。这里再强调一次顺序先扣库存再插订单头再插明细。为什么不是先插订单再扣库存因为扣库存是先决条件库存不够就不该有订单。如果反着来你还要在库存不足时把已插入的订单删除多一次操作也多一个出脏数据的窗口。4. 避坑笔记驱动、乱码、线程与并发五个让项目跑不起来的排查方向4.1 驱动版本与MySQL版本不匹配ClassNotFoundException只是开始现象刚把项目跑起来加载驱动那步就报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或Connection建立时报Communications link failure。原因你用了MySQL 8.0的驱动包但类名写的是老版com.mysql.jdbc.Driver又或者把5.x驱动的类名误写成8.x格式。MySQL 8.0的Connector/J把驱动入口改成了com.mysql.cj.jdbc.Driver旧名字虽然还兼容但会打deprecated警告。很多网上的旧教程写的是旧类名跟着抄就中招。解决在lib目录里确认Connector/J的版本然后核对DBHelper里Class.forName的类名8.x用com.mysql.cj.jdbc.Driver5.x用com.mysql.jdbc.Driver。如果还报Communications link failure检查3306端口是否被占用在Windows命令行执行netstat -ano | findstr 3306看端口归属常见冲突来源是自己电脑装了多个MySQL服务或者XAMPP里的MySQL也在跑。把多余的服务停掉或者改端口问题立刻消失。4.2 中文乱码从连接串到表字符集三层都要统一现象界面上商品名显示或写入数据库再读出来是乱码但MySQL Workbench里看数据又是正常的。原因字符集断裂。连接串没加useUnicodetruecharacterEncodingutf8或者数据库表建的字符集是latin1又或者MySQL服务端character_set_server默认值不对。解决拆三步排查。第一步看连接串有没有带characterEncodingutf8第二步在建库SQL里显式DEFAULT CHARACTER SET utf8mb4第三步用SHOW VARIABLES LIKE character_set%看服务端当前值如果character_set_server是latin1就在my.ini的[mysqld]段加character-set-serverutf8mb4并重启服务。改完之后把旧表DROP重建因为ALTER TABLE CONVERT TO CHARACTER SET在数据已写入时经常不生效。这个坑我踩过不止一次后来养成习惯建库SQL里永远写明字符集连接串里永远写全参数不依赖任何默认值。4.3 导入项目后全是红叉Eclipse里最常见的三种原因现象从压缩包导入一个完整项目Eclipse的Package Explorer里项目名上一个红叉编译不通过甚至运行时提示找不到或无法加载主类。原因大部分时候不是代码问题而是环境问题。第一缺jar项目lib目录里mysql-connector的jar没有被加进Build Path。第二JDK版本不对项目原本在JDK 8下编译你本地Eclipse默认却用了JDK 17编译级别不匹配。第三编码不对项目原本是UTF-8而Eclipse默认workspace编码是GBK中文注释全部变乱码进而导致编译报错。解决右键项目 → Properties → Java Build Path确认Libraries里有mysql-connector jar再检查Project Facets或Compiler里的Java版本与本地JDK一致。编码问题在Window → Preferences → General → Workspace里把Text file encoding改成UTF-8然后项目右键Properties → Resource也改成UTF-8。改完编码后记得Project → Clean全量重编译一次红叉一般就消失了。如果还提示找不到主类检查你右键Run As的是不是包含main方法的类Eclipse里直接Run一个没有main的类就是这样报的。4.4 Swing界面卡死与数据不刷新UI线程的坑现象点查询按钮后窗口直接白屏无响应或者数据库里明明有数据表格就是空的要多点几次按钮才出来。原因典型的Swing并发问题。Swing是单线程模型所有UI更新必须在Event Dispatch ThreadEDT上执行。如果你在查询的监听器里写了一个很慢的JDBC循环整个界面就会冻结因为UI线程被数据库查询占住了。反过来如果在后台线程里更新了数据模型却没有用SwingUtilities.invokeLater把刷新动作回抛给EDT表格也不会重绘就会出现数据有了但界面不更新的玄学现象。解决耗时的数据库操作放进SwingWorker拿到结果后用invokeLater再刷模型。一个稳妥的写法SwingWorkerVoid, Void worker new SwingWorkerVoid, Void() { Override protected Void doInBackground() { loadProductTable(model); return null; } Override protected void done() { table.updateUI(); } }; worker.execute();说明SwingWorker的doInBackground跑在后台线程done方法自动回到EDT。这个写法比直接new Thread更安全适合课设展示时保护界面不卡死。如果你用普通new Thread记得在run()结束前调用SwingUtilities.invokeLater(() - table.updateUI())。另外JTable刷新有一套组合动作model.setRowCount(0)后addRow最后table.updateUI()少一个都可能出现表格显示不全。4.5 扣库存扣出负数事务、并发和唯一约束的联动防守现象两个收银台同时卖同一件商品最后库存变成-1或者订单表里同一个order_no重复出现。原因没有在查库存→扣库存这段事务里锁行。我在第三章代码里写了SELECT ... FOR UPDATE这一步很关键它让MySQL InnoDB在查到库存记录时就把行锁住直到commit或rollback才释放。如果不锁两个事务可以同时读到stock1然后各自扣1最后变成-1。order_no重复则是没用唯一索引手写的时间戳随机数偶尔会撞。解决第一扣减库存的UPDATE语句本身就带上条件AND stock?返回值为0就说明库存不足直接回滚第二给tbl_sale_order.order_no加UNIQUE KEY这在建表SQL里已经写了第三把事务隔离级别设为READ_COMMITTED或REPEATABLE_READ并配合FOR UPDATE。这些措施叠起来超卖基本能堵死。最后记得事务一定要在finally里做兜底回滚不然异常时连接归还到池里下一次用的还是半截事务。5. 从课设到作品验证、优化与打包里值得加的功夫5.1 验收清单怎么证明你的系统扛得住拿到代码先别急着交按四条路径验证。单用户全流程登录→新增商品→下单→扣库存→看订单明细确认每一步数据对得上。双开并发验证启动两个客户端实例对同一商品同时下单看库存是否出现过负数这个测试能直接暴露锁问题。断网验证事务在下单过程中拔掉网线或停掉MySQL服务程序必须回滚库存不能少。时间字段检查确认sale_time是北京时间而非UTC少8小时这个看连接串里serverTimezone参数就行。5.2 优化方向从DriverManager换到连接池项目跑通后最值得做的一步是把DriverManager换成连接池。常见做法是引入HikariCP或Druid好处是连接复用不用每次请求都建立TCP连接。配置参数很简单HikariConfig config new HikariConfig(); config.setJdbcUrl(url); config.setUsername(root); config.setPassword(123456); config.setMaximumPoolSize(10); config.setConnectionTimeout(3000); HikariDataSource ds new HikariDataSource(config);参数说明maximumPoolSize设10对课设单机场景足够connectionTimeout设为3000毫秒连接池忙时快速失败而不是无限等。数据库访问性能的另一个大头是批量插入销售明细如果逐条executeUpdate10个商品要10次网络往返用PreparedStatement的addBatch()然后executeBatch()一次提交全部性能能快一个数量级。5.3 打包与交付Eclipse导出jar加启动脚本交付一个能双击运行的程序比交源码更让老师省心。做法是右键项目 → Export → Runnable JAR fileLaunch configuration选你带main方法的类Library handling选Package required libraries into generated JAR这样mysql-connector会打进jar包。导出后写一个start.bat的启动脚本echo off java -jar supermarket.jar pause说明前提是本机装了JDK且配置了JAVA_HOME环境变量。如果目标机器是64位Windows建议用jlink生成一个精简运行时但课设阶段打jar就够。README里务必写清楚JDK版本、MySQL版本、驱动版本、建库SQL执行方式防止别人换了环境跑不起来。这一步不是形式主义很多人把自己的项目拷到另一台电脑就跑不了多半就是没写环境依赖。这套系统做到最后我的感受是真正花时间的不是代码本体而是那些看不见的边界条件——并发超卖、事务回滚、编码一致性。吃过一次亏后我养成了固定习惯所有连接串参数写全所有事务走同一套try-commit-rollback-finally模板所有表设计先画关系再写SQL。你有更好的排查思路或者踩到过更隐蔽的坑按自己的经验补进这个模板就好。希望帮到你。本文还有配套的精品资源点击获取