☰
Java+SQL Server 2000超市管理系统源码跑通指南
2026/10/1 10:37:08 网站建设 项目流程

简介:这是一份基于Java与SQL Server 2000开发的超市管理系统完整源代码包,适合Java初学者、课程设计者以及需要了解桌面数据库管理系统架构的开发者。系统涵盖库存、销售、客户、员工等典型业务模块,代码中涉及Swing图形界面、JDBC数据库交互、MVC分层设计、异常处理及多线程等关键知识点,并配有数据库脚本,便于直接运行和二次开发。资源包共218个文件,压缩后约2.76MB,主要包含63个class编译文件、18个java源文件、相关SQL脚本、HTML页面、JAR包以及各类工程辅助文件,可对照源码与编译产物梳理完整项目结构。已有551人学习下载。包内提供的源码与数据库脚本能帮助读者理解Java桌面开发与SQL Server 2000的衔接方式,同时参考其中的窗口管理和业务处理逻辑,可作为课程设计或毕业设计的实用素材。

1. 老古董Java超市系统,为什么还有人要它

一看到「Java+SQL Server 2000超市管理系统源代码.zip」这种标题,第一反应往往是:2000年的技术栈还有人要?但这类老源码在课程设计、毕业设计、旧系统二次开发里恰恰是刚需,不少同学拿到的就是一份带数据库备份和Swing界面的完整项目。它的价值不在技术新,而在结构全:JDBC连接、DAO层、业务层、事件驱动的界面层一应俱全,看懂它等于把Java基础过了一遍。下面按实际操作顺序,把数据库还原、驱动配置、代码骨架、避坑记录和验证方法一次讲完,目标是让你照着走一遍就能跑起来。

2. 还原SQL Server 2000数据库:决定成败的第一步

拿到zip后别急着打开IDE,先解压找数据库文件。这类源码通常会在压缩包里带一个db或database目录,里面是.bak备份文件或分离出来的.mdf/.ldf文件对。这两种形态处理方式完全不同:bak要走还原,mdf/ldf要附加。SQL Server 2000的备份和之后的版本兼容性一般,用新版SSMS还原时经常报“cannot open backup device”或“媒体集有误”,所以先确认目标机器的SQL Server版本和文件格式,比上来就执行命令要重要。

2.1 先分清数据库文件是备份还是附加:bak与mdf处理方式不同

先看文件后缀。带.bak的是备份文件,对应“还原”操作;带.mdf和.ldf的是数据文件和日志文件,对应“附加”操作。这套判断决定命令写法:还原要指定逻辑名和物理路径,附加只需要给文件路径。老资料里经常出现把.mdf直接当bak文件处理结果报错的情况,所以拿到文件先看扩展名,别猜。

区分逻辑其实很简单:备份文件是数据库的“快照+日志切片”,里面打包了文件路径信息;数据文件本身是数据库正在用的文件。还原的过程相当于把快照重新铺到新环境;附加的过程相当于告诉实例“这个文件已经是一个数据库了,直接挂进来”。SQL Server 2000时代还没有“文件流”概念,路径和实例绑定很死,所以换机器后还原更容易踩路径冲突。

2.2 还原命令的两种写法与WITH MOVE参数

如果你拿到的是bak文件,常见做法是在查询分析器里执行RESTORE命令。SQL Server 2000的查询分析器对中文环境支持一般,但命令本身很稳定。示例:

RESTORE DATABASE SuperMarket FROM DISK = 'D:\backup\SuperMarket.bak' WITH MOVE 'SuperMarket_Data' TO 'D:\MSSQL\data\SuperMarket_Data.mdf', MOVE 'SuperMarket_Log' TO 'D:\MSSQL\log\SuperMarket_Log.ldf', REPLACE

这里关键是WITH MOVE:bak文件里记录的是原来那台机器的物理路径,目标机器上目录结构不一样,不重新指定就会报“无法打开物理文件”或“文件已存在”。MOVE后面的第一个参数是逻辑文件名,可以在还原前用RESTORE FILELISTONLY查一下;第二个TO是目标机器上的实际路径,目录必须提前建好。REPLACE表示如果目标库已存在就覆盖重来,适合反复导入的场景,但生产库慎用。

如果拿到的是mdf/ldf文件对,直接附加:

sp_attach_db 'SuperMarket', 'D:\MSSQL\data\SuperMarket_Data.mdf', 'D:\MSSQL\log\SuperMarket_Log.ldf'

sp_attach_db是SQL Server 2000自带的系统存储过程,参数就是要附加的库名和文件路径序列。注意日志文件可以缺省,但缺省后系统会自动生成日志,数据文件损坏时可以用这种方式抢救,但事务日志会丢,之后运行如果出现LSN不一致报错,还原回来再附加是唯一解。附加成功后会立刻出现在企业管理器的数据库列表里,不需要再导入数据。

2.3 还原后立刻验证:表清单与关键表字段

数据库挂上后别急着跑程序,先在查询分析器里跑一遍表清单确认不空:

SELECT name FROM sysobjects WHERE xtype = 'U'

SQL Server 2000没有sys.tables,用sysobjects是正统方式。跑出来清单后,重点看三类表:用户/员工表、商品/库存表、销售/明细表。管理系统只要这三类表里有记录,说明备份内容完整;如果查询结果为空但库已还原,大概率是附加错了文件或文件的逻辑名对不上。

然后再看一眼排序规则,中文环境常见的是Chinese_PRC_CI_AS。老系统在GBK环境下建的库,编码默认GBK,这直接关系到后面JDBC连接时的编码参数。这一步不做,后期中文乱码会牵扯出一整串问题。到此数据库就绪,下一步要处理的是Java这一侧:驱动、连接配置和启动顺序。

3. 用JDBC连上2000年的SQL Server:配置改法与启动顺序

数据库还原完成只是第一步,真正让“数据库存在”变成“程序能用”,靠的是驱动和连接字符串。SQL Server 2000那个年代的JDBC驱动主流是微软官方驱动和开源的JTDS。老源码包大概率会把jar放在项目根目录的lib下,启动前先看lib目录是不是空的,这一步比改代码还关键。

3.1 驱动选择:微软旧驱动和JTDS的取舍

经常见到的驱动写法有这么两组,对应不同的类名和URL:

微软旧驱动(sqljdbc.jar,classic版):

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); // URL: jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=SuperMarket

JTDS(jtds开源版):

Class.forName("net.sourceforge.jtds.jdbc.Driver"); // URL: jdbc:jtds:sqlserver://localhost:1433/SuperMarket

两种写法在SQL Server 2000下都能跑,但JTDS在处理老版本TDS协议、整数类型映射和连接池兼容性上更省心。微软旧驱动在新版JDK下容易因模块化机制加载失败,而JTDS对JDK 8依然友好。如果源码包两个jar都有,优先用JTDS,并把老代码里的驱动类和URL全部替换成JTDS格式。别小看这个替换,绕过了后面一大半的奇怪报错。

3.2 数据库连接类的标准写法与db.properties

不管源码包用哪种方案,连接管理类的结构都差不多:一个DbUtil类,负责加载驱动、拿连接、关闭连接。常见写法如下:

public class DbUtil { private static final String DRIVER = "net.sourceforge.jtds.jdbc.Driver"; private static final String URL = "jdbc:jtds:sqlserver://localhost:1433/supermarket"; private static final String USER = "sa"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException e) {} } if (conn != null) { try { conn.close(); } catch (SQLException e) {} } } }

Class.forName的作用是让驱动类在静态代码块里把自己注册到DriverManager;这一步在JDBC 4.0之后可以省略,但2000年写的代码习惯沿用这个写法,保留它没有坏处。关闭连接的顺序必须是rs→stmt→conn,反着关在连接复用时会报“ResultSet已关闭”或“连接已被占用”。如果压缩包里的db.properties文件已经存在,把上面的四个常量改成从Properties读取,项目迁移到别的数据库时只需改文件不需要改代码,这个改造我在第6章会再展开。

3.3 启动顺序:先服务端后客户端,别反着来

跑老项目时最常见的一个错误顺序是:先在IDE里启动客户端,再想起来数据库没起,然后报错后反过头来找数据库。正确顺序应该是:

  1. 启动SQL Server 2000服务(开始菜单里的SQL Server服务管理器,选实例后点Start)
  2. 如果源码包里有Server入口类(类名里常带Server字样的main方法),先启动它,它负责网络监听
  3. 最后启动LoginUI/MainUI这类带界面的入口类

另外命令行或IDE里要确认Java环境变量配置是对的:JAVA_HOME指向本机安装的JDK,PATH里包含%JAVA_HOME%\bin,CLASSPATH不要写死,用通配符更省事。这一步常常被忽略,但老项目对CLASSPATH的敏感度比新项目高得多,漏掉一个lib目录就起不来。

有个判断技巧:先看有没有Server入口类。如果有一个带Server字样且main方法里在等待Socket连接的类,说明这是经典的客户端服务端模式;没有的话,客户端直接连SQL Server。确认模式后,启动顺序就很清晰了。服务端没起来但客户端先跑,常见的结果是抛“连接失败”,然后你在这台机器上排查半天,最后发现只是顺序反了。

4. 读懂这套代码的骨架:从登录到结账的关键调用链

跑起来之后,别满足于“看到窗口”。这套系统的核心价值是三层结构——界面层、业务层、数据访问层。读代码时从登录界面一步步跟到数据库,你就掌握了一次完整的Java课程设计案例源码的解读方法。读懂调用链之后,改功能、加报表、修bug才有下手点。

4.1 三层结构一眼认出来:从包名到类名

大多数Java+Swing课程设计源码的包结构长这样:

src/ com/supermarket/ db/DbUtil.java model/User.java model/Product.java model/Sale.java dao/UserDao.java dao/ProductDao.java dao/SaleDao.java ui/LoginUI.java ui/MainFrame.java ui/SaleUI.java

ui包处理鼠标事件和窗口切换,dao包直接写SQL,model只是数据载体。老课程设计常常把业务逻辑直接塞在ui里,service包经常是空的,这是判断代码质量的一个关键信号。如果你的压缩包里没有service包,别惊讶,改的时候自己补上就行。识别出这层结构后,新增功能时就知道该动哪个文件,不会到处乱翻。

4.2 登录到结账:一次完整的数据流

我从登录开始讲一次数据流,命名按常见版本示意,实际类名可能不同但方向一致:LoginUI的登录按钮拼装User对象,调用UserDao.authenticate(username, password),UserDao内部执行SELECT语句返回找到的User,LoginUI判断结果后setVisible切换主界面。结账流程类似:SaleUI收集购物车,SaleDao.insertSale(sale, items),方法里先插入销售主表再循环插入明细表,最后更新库存。

下面用JDK 5+的泛型写法示意,老JDK 1.4里把List<SaleItem>换成Vector即可,逻辑不变。这里最值得读的是insertSale这个方法的JDBC写法:

public boolean insertSale(Sale sale, List<SaleItem> items) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DbUtil.getConnection(); conn.setAutoCommit(false); // 事务从这里开始 ps = conn.prepareStatement( "INSERT INTO sale(no, operator, total, sale_date) VALUES (?, ?, ?, getdate())", Statement.RETURN_GENERATED_KEYS); ps.setString(1, sale.getNo()); ps.setString(2, sale.getOperator()); ps.setBigDecimal(3, sale.getTotal()); ps.executeUpdate(); rs = ps.getGeneratedKeys(); int saleId = -1; if (rs.next()) { saleId = rs.getInt(1); } for (SaleItem item : items) { ps = conn.prepareStatement( "INSERT INTO sale_item(sale_id, product_id, qty, price) VALUES (?, ?, ?, ?)"); ps.setInt(1, saleId); ps.setInt(2, item.getProductId()); ps.setInt(3, item.getQty()); ps.setBigDecimal(4, item.getPrice()); ps.executeUpdate(); ps = conn.prepareStatement( "UPDATE product SET stock = stock - ? WHERE product_id = ?"); ps.setInt(1, item.getQty()); ps.setInt(2, item.getProductId()); ps.executeUpdate(); } conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) {} } e.printStackTrace(); return false; } finally { DbUtil.close(conn, ps, rs); } }

这段代码里有两个地方值得细看。第一个是setAutoCommit(false):insert主表、insert明细、update库存这三条SQL要么全部成功要么全部失败,这就是Java保证数据一致性的最基础实现,也是在老项目里最容易改出问题的地方。第二个是PreparedStatement的参数绑定,老代码里如果看到字符串拼接SQL,建议全部改成这种写法,一方面防转义注入,另一方面类型转换交给驱动而不是手写引号。注意SQL Server 2000的datetime列用getdate()生成,在Java侧用setTimestamp也能写,但getdate()更省事,也避免时区换算出问题。

注意:部分老驱动对getGeneratedKeys支持不稳定,拿不到自增主键时,改为INSERT执行后立刻执行SELECT SCOPE_IDENTITY(),用结果集的第一个int作为saleId。

4.3 改代码时的两个关键约束:事务边界与金额计算

读完调用链后想动手改老代码,先记住两个容易让你后悔的约束。事务边界:任何“先写主表再写子表”“先扣库存再记流水”的操作,都必须包在一个Connection里,事务边界就是那条Connection的生命周期。很多老项目翻车的原因是每执行一句SQL就new一个Connection,第二句失败后第一句已经提交,库存与销售额对不上。接手这类代码时,先把散落的语句收进同一个方法,再加事务。

金额计算是第二个约束:老代码里经常出现double类型存储金额,结账一旦涉及折扣、小数,double的浮点误差在期末对账时就会现形。改动建议是把金额、单价、折扣率全部换成BigDecimal,SQL侧对应字段用decimal而不是float。这属于Java基础里的老生常谈,可在这种课程设计源码里几乎每份都犯,你改掉一个就相当于帮项目补了一个大洞。这两个约束只花半天时间理顺,后续改代码不会越改越乱。

5. 历史遗留项目跑通避坑记录:五个典型故障与排查顺序

即使数据库还原成功、代码也能编译,真正跑起来还会遇到一堆环境问题。这些问题看起来随机,实际上都有固定的触发条件,按下面顺序排查能省很多时间。排查顺序的总体原则是:先依赖,再网络,再编码,再连接,最后看JDK版本。从后往前倒着查,很容易陷入“日志一直在报同一个错”的循环。

5.1 第1个坑:ClassNotFoundException,驱动jar没进lib

现象:启动即抛java.lang.ClassNotFoundException: com.microsoft.jdbc.sqlserver.SQLServerDriver或net.sourceforge.jtds.jdbc.Driver。

原因:驱动jar不在classpath。IDE导入源码时经常漏掉lib目录,或者lib目录里的jar没有被选为依赖;命令行运行时则是CLASSPATH没写对。

解决:先在lib目录确认jar存在且名字拼写正确,再在IDE里把它Add to Build Path。命令行运行时用通配符,别手写全路径:

java -cp "lib/*;out/production/SuperMarket" com.supermarket.ui.LoginUI

注意Windows下分隔符是分号,Linux/macOS是冒号。分号写错,下一个异常还是ClassNotFoundException。这类依赖问题说穿了很简单,但不少同学卡在这一步一整晚。

5.2 第2个坑:1433端口连不上,实例名和网络服务的问题

现象:抛SQLException或Connection refused,程序卡在连接上迟迟不返回。

原因:SQL Server 2000默认实例监听1433端口,但命名实例默认是动态端口;另一个常见原因是服务没启动或防火墙拦了1433。

解决:先确认服务管理器里实例状态是Running;再在命令提示符下执行:

telnet 127.0.0.1 1433

能连通说明服务和端口都正常。连不通就检查企业管理器里的服务器网络实用工具,启用TCP/IP,并把端口固定为1433。命名实例的URL要带上instance参数,JTDS格式是jdbc:jtds:sqlserver://localhost:1433/supermarket;instance=你的实例名。注意这里数据库名大小写不影响连接,SQL Server不区分大小写,但实例名是区分大小写的。

5.3 第3个坑:中文乱码,GBK与UTF8对不上

现象:程序界面中文显示正常,但读出来的是乱码;或者反过来界面显示乱码,数据库里正常。

原因:整条链路编码不一致。SQL Server 2000库多数是GBK体系的Chinese_PRC_CI_AS排序规则,源码文件可能是GBK,而JVM的默认编码在中英文系统上表现不同,JDK 18以后默认强制UTF-8。换机器后链路错位,乱码就现形。

解决:如果程序本身按GBK编写,在IDE的VM options或命令行加-Dfile.encoding=GBK;SQL语句里出现的字符串改为N'xxx'方式,强制按Unicode处理。注意这不是玄学,是编码链路的对齐问题。老系统没有统一编码设计,能跑通说明数据库和JVM恰好对齐了,一旦换机器先查这一条。乱码问题排查起来很容易绕远,先从数据库排序规则和JVM启动参数两头发,比逐个表改字段快得多。

5.4 第4个坑:连接空闲后被回收,程序放一会儿再用就报错

现象:程序刚启动没问题,隔一段时间(常见是几十分钟)再操作就抛“对象已关闭”或“连接已重置”。

原因:连接长时间空闲后,数据库端或网络链路会将其关闭;程序里长期持有Connection而不释放,空闲超时一到连接就失效了。这不是代码里的业务bug,而是连接生命周期管理问题。

解决:每次操作用完就在finally里从ResultSet到Statement再到Connection依次close,不要让连接长期持有。老代码里如果看到成员变量里保存Connection然后到处复用的,改成方法内局部变量。这个习惯比加连接池配置更基础,先修这个再考虑池化。连接池不是万能药,池里的空闲连接同样会被数据库回收,池化之后还要配置空闲检测参数。

5.5 第5个坑:JDK版本过高,UnsupportedClassVersionError

现象:启动即报UnsupportedClassVersionError,提示类文件版本号与运行时版本不一致。

原因:源码包编译时用的JDK版本较低,本机却装了JDK 11或17。老代码很多是JDK 1.4/1.5时期写的,较新JDK运行时会直接拒绝加载。

解决:装一个JDK 8,并在IDE里把项目SDK和编译级别都切到JDK 8。装了之后别忘了JAVA_HOME环境变量配置要同步指向新装的JDK,PATH里不能还残留高版本JDK的路径。这里有个很常见的翻车现场:命令行java -version看到的是新版本,IDE里却用老SDK编译,两者不一致导致“能编不能跑”。用java -version确认当前版本,再看IDE里Project Structure,命令行、项目SDK、编译级别三个地方一致才稳。这类问题的排查顺序也常被Java面试题问到,本质就是环境变量、驱动加载、JDK版本这几个固定原因,先环境后代码最省时间。如果再遇到组合故障:驱动没加载、端口不通、编码不对一起出现,按这里的顺序重头过一遍,通常比盯着一个点看更快。

6. 用最小用例验证系统:两种检查思路和一个改造技巧

跑起来不等于能用。想确认这套源码真的可用,我习惯用两个最小用例,只花十分钟但能覆盖主链路。这两个用例只走最核心的路径:登录和结账。数据库操作类项目里,这两条路径能把连接、查询、事务、更新全部串起来。

6.1 两个最小用例:登录后开单与盘点报表

用例一是登录后开一张销售单:输入商品、数量,结账,然后去数据库查库存和销售汇总。手工算一下销售金额,跟界面结账显示的值做对比。一次能对上,说明JDBC读写和事务提交都在正常工作。

SELECT product_id, stock FROM product WHERE product_id = 1; SELECT SUM(amount) AS total FROM sale WHERE sale_date = '2025-01-01';

用例二是盘点报表查询:报表模块拉出来的数据,跟上面SQL查出来的数字逐项核对。注意订单量大的库还有金额字段类型转换的坑,如果报表显示的是浮点数而数据库里是decimal,看到0.9999这种数字别奇怪,先检查取数代码里的类型映射。两个用例都通过,这套系统才算真正跑到你自己手里了。

6.2 一个最小改造:把连接信息抽到配置文件

如果确认主链路能跑通,我建议你顺手做一个最小的改造:把DbUtil里写死的连接信息抽出去。在src目录建db.properties:

jdbc.driver=net.sourceforge.jtds.jdbc.Driver jdbc.url=jdbc:jtds:sqlserver://localhost:1433/supermarket jdbc.user=sa jdbc.password=123456

然后DbUtil里改成Properties加载,只贴核心片段:

Properties props = new Properties(); try (InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties")) { props.load(in); } Class.forName(props.getProperty("jdbc.driver")); String url = props.getProperty("jdbc.url");

提示:这个片段用了JDK 7的try-with-resources,如果编译级别低于JDK 7,把它改回try/finally,在finally里关闭InputStream。

这样以后再迁到别的机器或换数据库实例,只动db.properties一个文件,不用再碰编译产物。这个改造本身不难,但它让整套源码具备了交付能力——别人拿到手只需要改一个文件配置就能跑。数据库文件、依赖jar、配置文件三者分开管理,是接手老项目时最值得养成的习惯。

我的习惯是先验证主链路再改代码,改之前先跑通最小用例;每改一步都回到用例上验证,这样出了问题能第一时间定位是数据库侧还是Java侧。老项目跑通流程比读懂每一行更重要,先把“登录→开单→结账→对账”这条链走顺,再回头细读代码、做改造也不迟。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询