资讯动态

Java JDBC手写事务与SQL Server 2000兼容实践指南

发布时间:2026/10/5 11:23:32 来源:尧图企业网站定制
简介这是一套面向Java初学者与课程设计者的超市管理系统实战源码基于Java桌面应用开发技术栈与SQL Server 2000数据库完整覆盖库存管理、销售记录、客户与员工信息维护等核心业务场景助力理解企业级小型管理系统的架构逻辑与落地实现。资源共218个文件含63个编译后class文件、18个主功能java源码如CustomerWindow、AdminWindow、UserManagement等以及sql数据库脚本、html界面文件、gif图标资源和jar依赖包整体压缩包仅2.76MB轻量易部署。已有551人学习下载代码结构体现典型Swing GUIJDBC直连MVC分层思想包含多版本备份源码java~1~至java~67~便于对比演进过程同时附带uml_cs建模文件与dep2依赖分析适合边读源码边梳理模块关系、调试运行并拓展报表或预警功能。1. 这不是“老古董演示项目”Java SQL Server 2000 超市管理系统源码的真实价值在哪你搜到这个压缩包时大概率正面临三类真实场景一是高校课程设计硬性要求用「SQL Server 2000 JDK 1.4」环境交付教务系统还没升级二是接手某县镇级老牌超市的遗留系统维保服务器上跑着 Windows 2000 Server SQL Server 2000 SP4连远程桌面都得开 Terminal Services三是面试被问“有没有做过完整三层架构的进销存”而你手头只有 Spring Boot 项目——这时候翻出这套代码不是怀旧是补上JDBC 手写事务、SQL Server 2000 特有语法、Windows 身份验证集成这三块被现代框架遮蔽的硬功夫。它不提供 REST API、没有 Maven 依赖管理、不用 Hibernate 自动生成 SQL但每行conn.createStatement()后跟着的executeUpdate()都在教你当数据库连不上时SQLException.getSQLState()返回的S0001比任何日志都直白当库存扣减失败setAutoCommit(false)和rollback()的调用顺序决定这笔账到底算不算数。这不是过时技术是数据库交互的底层契约——尤其当你需要给国产信创数据库做 JDBC 兼容适配时这套代码里对ResultSetMetaData的手动列遍历、对datetime类型的SimpleDateFormat处理全是现成的避坑手册。2. 从解压到可运行本地复现的最小闭环路径这套系统不是开箱即用的 Docker 镜像它的可运行性完全依赖于四个刚性条件JDK 版本锁定、SQL Server 2000 实例存在、Windows 身份验证启用、数据库用户权限精确到表级。我一般会先确认这四点再解压否则花两小时调环境不如重装系统。2.1 环境准备为什么必须用 JDK 1.4.2 和 SQL Server 2000 SP4JDK 1.4.2 是分水岭版本它首次内置assert关键字但尚未支持泛型java.util.Date还没被Calendar全面接管而源码中大量使用Date.toString()做日志输出——若用 JDK 8 运行toString()返回格式已变Mon Jan 01 00:00:00 CST 2000→Mon Jan 01 00:00:00 CST 2000表面一样但内部getYear()返回值偏移量不同导致入库时间戳错位。SQL Server 2000 SP4 则解决了一个致命 bugSP3 及之前版本在SELECT COUNT(*) FROM table WITH (NOLOCK)语句中若表名含空格或特殊字符驱动会抛出SQLState: S0001错误而非正确返回结果。源码中StockDAO.java第 87 行正是这种写法SP4 才能兼容。提示不要试图用 SQL Server 2019 兼容模式运行——xp_cmdshell权限控制、text数据类型处理逻辑、sp_addlogin存储过程参数结构均不兼容。必须用原版安装介质或从微软官方归档库下载sql2ksp4.exe。2.2 数据库初始化手动执行.sql脚本的三个关键动作解压后你会看到DBScript.sql文件但它不能直接osql -E -i DBScript.sql一键执行。必须分三步先建库并指定排序规则CREATE DATABASE SuperMarketDB ON (NAMESuperMarket_Data, FILENAMEC:\MSSQL\Data\SuperMarket_Data.mdf) LOG ON (NAMESuperMarket_Log, FILENAMEC:\MSSQL\Data\SuperMarket_Log.ldf) COLLATE Chinese_PRC_CI_AS;注意COLLATE Chinese_PRC_CI_AS不可省略。源码中商品名称查询用WHERE name LIKE %牛奶%若用默认SQL_Latin1_General_CP1_CI_AS中文模糊匹配会失效返回空结果。创建 Windows 登录用户并映射EXEC sp_grantdbaccess BUILTIN\Administrators, sa; EXEC sp_addrolemember db_owner, sa;源码连接字符串为jdbc:sqlserver://localhost;databaseNameSuperMarketDB;integratedSecuritytrue;依赖 Windows 身份验证。若用 SQL 账户密码方式需修改DBUtil.java中getConnection()方法并重写login.jsp的认证逻辑。执行建表与初始数据DBScript.sql中INSERT INTO users VALUES(admin,123456,管理员)这行必须保留——这是唯一登录凭证。密码是明文 MD534819d7beeabb9758d85b108a4a9c1e7但源码未调用MessageDigest而是直接存字符串所以不能用在线 MD5 工具生成。2.3 编译与部署绕过 IDE 直接用javac的实操命令项目结构是典型 JSP/Servlet 模式src/下放 Java 类WebRoot/下放 JSP 和WEB-INF/web.xml。不要导入 Eclipse 或 IntelliJ——它们会自动添加servlet-api.jar依赖而源码实际引用的是Tomcat 4.1.31自带的servlet.jarJDK 1.4.2 时代产物。正确编译命令如下# 进入 src 目录 cd D:\SuperMarket\src # 编译所有 Java 文件显式指定 servlet.jar 路径 javac -classpath D:\Tomcat4.1\common\lib\servlet.jar;D:\SuperMarket\lib\sqlserver2000.jar *.java # 将 class 文件复制到 WebRoot\WEB-INF\classes xcopy *.class D:\SuperMarket\WebRoot\WEB-INF\classes /Y其中sqlserver2000.jar是微软官方 JDBC 驱动msbase.jar,mssqlserver.jar,msutil.jar三件套合并版不是sqljdbc4.jar。若用新版驱动DriverManager.getConnection()会抛出No suitable driver found——因为 URL 格式已从jdbc:microsoft:sqlserver://变为jdbc:sqlserver://。3. 连接池与事务手写 JDBC 里的生存法则这套系统没用 C3P0 或 DBCP而是用DBConnectionPool.java实现了最简连接池。它不追求高并发但把连接泄漏检测、SQL 注入防护、事务边界控制三件事做透了。理解它比背十遍 Spring Transaction 注解更有实战价值。3.1 连接池的“保命机制”如何防止连接耗尽DBConnectionPool.java中freeConnection()方法有段关键逻辑public void freeConnection(Connection con) { if (con null) return; try { if (!con.isClosed() !con.getAutoCommit()) { con.rollback(); // 强制回滚未提交事务 } con.close(); // 关闭物理连接 availableConnections.add(con); // 归还到空闲队列 } catch (SQLException e) { // 记录日志但不抛出避免调用方崩溃 System.err.println(Free connection error: e.getMessage()); } }注意!con.getAutoCommit()判断如果业务代码忘记commit()连接归还时会自动rollback()防止脏数据滞留。而availableConnections是Vector而非ArrayList因为Vector的add()方法是synchronized在 Tomcat 4.1 单线程模型下避免了并发修改异常——这是 JDK 1.4.2 时代的妥协方案。3.2 事务控制的“铁律”为什么StockDAO.updateStock()必须用Connection参数传递看StockDAO.java第 121 行public boolean updateStock(Connection conn, String goodsId, int changeAmount) throws SQLException { String sql UPDATE goods SET stock stock ? WHERE id ?; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, changeAmount); ps.setString(2, goodsId); int rows ps.executeUpdate(); ps.close(); return rows 0; }这里conn由SaleService.java的doSale()方法传入而非自己getConnection()。原因在于一笔销售涉及goods表库存更新、sale_record表插入、account表余额扣减三张表操作必须在同一个Connection上完成否则conn1.commit()和conn2.commit()无法保证原子性。源码中SaleService.doSale()开头就conn.setAutoCommit(false)结尾统一commit()或rollback()——这是 JDBC 事务的原始形态也是理解分布式事务XA协议的基础。3.3 SQL 注入的“手工防护”StringUtil.filterSQL()的真实作用StringUtil.java中这个方法常被误解为“防注入万能盾”public static String filterSQL(String str) { if (str null) return null; str str.replaceAll(;, ); // 删除分号 str str.replaceAll(--, ); // 删除注释 str str.replaceAll(/\\*, ); // 删除 /* */ str str.replaceAll(\\*/, ); // 删除 */ str str.replaceAll(, ); // 单引号转义 return str; }它只对String类型参数生效且仅处理常见攻击符号。真正安全的做法是全部改用PreparedStatement。源码中UserDAO.login()仍用拼接 SQLSELECT * FROM users WHERE username username 此时filterSQL()才起作用。但StockDAO所有方法都用?占位符——这才是正确姿势。记住filterSQL()是补救措施不是替代方案。4. 常见问题排查那些让新手卡住 3 小时的“玄学错误”这套系统最折磨人的不是功能缺陷而是环境差异导致的“看似正常却死活登不进去”。以下是我在 7 所高校实训基地现场支持时记录的 5 类高频问题按现象→原因→解决顺序排列每条都经过真实复现。4.1 现象启动 Tomcat 后访问http://localhost:8080/login.jsp显示 404原因web.xml中welcome-file-list配置为welcome-fileindex.jsp/welcome-file但项目根目录下只有login.jsp没有index.jsp。Tomcat 4.1 默认不加载login.jsp作为欢迎页。解决编辑WebRoot\WEB-INF\web.xml将welcome-fileindex.jsp/welcome-file改为welcome-filelogin.jsp/welcome-file重启 Tomcat。4.2 现象登录时提示 “数据库连接失败”但osql -E可以连通原因DBUtil.java第 32 行连接字符串为jdbc:microsoft:sqlserver://localhost:1433;DatabaseNameSuperMarketDB;SelectMethodcursor;其中SelectMethodcursor是 SQL Server 2000 驱动特有参数若 SQL Server 实例端口不是默认 1433比如被改为 1434此处未同步修改。解决检查 SQL Server 配置管理器中TCP/IP协议的端口号在连接字符串中显式写入如jdbc:microsoft:sqlserver://localhost:1434;...。4.3 现象商品入库后stock字段显示为NULL而非数字原因goods表中stock字段定义为int NULL但GoodsDAO.insert()方法中ps.setInt(5, stock)传入0而 SQL Server 2000 对int类型0和NULL区分严格。若前端表单未填库存值request.getParameter(stock)返回nullInteger.parseInt(null)抛出NumberFormatException事务回滚导致插入失败但页面无提示。解决在GoodsAction.java中增加判空String stockStr request.getParameter(stock); int stock (stockStr null || stockStr.trim().isEmpty()) ? 0 : Integer.parseInt(stockStr);4.4 现象销售单打印预览空白PDF 生成失败原因PrintUtil.java使用com.lowagie.textiText 1.3生成 PDF该版本依赖font.properties文件指定中文字体路径。源码中路径写死为C:/WINDOWS/Fonts/simsun.ttc若系统字体目录不同如 Win10 是C:\Windows\Fonts\simfang.ttf则文字渲染为空白。解决复制任意一个 TrueType 中文字体如simhei.ttf到D:\SuperMarket\WebRoot\fonts\修改PrintUtil.java第 45 行FontFactory.setFontPath(D:/SuperMarket/WebRoot/fonts/simhei.ttf);4.5 现象修改密码后新密码无法登录旧密码仍有效原因UserDAO.updatePassword()执行UPDATE users SET password ? WHERE username ?但password字段长度为varchar(20)而 MD5 加密后字符串长 32 位超出截断导致存储为34819d7beeabb9758d85b108a4a9c132 位被截成 20 位。解决用osql连接数据库执行ALTER TABLE users ALTER COLUMN password varchar(32);并确保DBScript.sql中建表语句已修正此字段长度。5. 从“能跑”到“可用”三个必须动手改造的关键模块这套代码的价值不在“拿来即用”而在“改得动、看得懂、能延伸”。我带学生做课程设计时总会让他们优先改造以下三处——它们直击现代开发痛点且改动成本低、见效快。5.1 将DBUtil.java升级为 JNDI 数据源告别硬编码连接字符串当前DBUtil.getConnection()直接拼接 URL密码明文写在代码里。改成 JNDI 只需两步在Tomcat 4.1\conf\server.xml的Context标签内添加Resource namejdbc/SuperMarketDB authContainer typejavax.sql.DataSource factoryorg.apache.commons.dbcp.BasicDataSourceFactory maxActive20 maxIdle10 minIdle5 initialSize5 driverClassNamecom.microsoft.jdbc.sqlserver.SQLServerDriver urljdbc:microsoft:sqlserver://localhost:1433;DatabaseNameSuperMarketDB; usernamesa passwordyour_password/修改DBUtil.java的getConnection()方法public static Connection getConnection() { try { Context ctx new InitialContext(); DataSource ds (DataSource) ctx.lookup(java:comp/env/jdbc/SuperMarketDB); return ds.getConnection(); } catch (Exception e) { throw new RuntimeException(Get connection from JNDI failed, e); } }注意需将commons-dbcp.jar和commons-pool.jar放入Tomcat 4.1\common\lib\并删除DBUtil.java中原有的Class.forName()加载驱动逻辑——JNDI 会自动处理。5.2 为SaleService.java添加日志追踪 ID解决多线程下单的排查黑洞当前日志只有System.out.println(Sale success)无法关联同一笔订单的多条日志。加一个TraceID工具类// TraceID.java public class TraceID { private static final ThreadLocalString traceId new ThreadLocalString() { Override protected String initialValue() { return UUID.randomUUID().toString().replace(-, ).substring(0, 12); } }; public static String get() { return traceId.get(); } public static void clear() { traceId.remove(); } }在SaleAction.java的doPost()开头和结尾调用protected void doPost(HttpServletRequest request, HttpServletResponse response) { System.out.println([ TraceID.get() ] Start sale process); try { // 原有业务逻辑 } finally { TraceID.clear(); // 必须清理避免线程复用时 ID 泄漏 } }这样每笔交易日志前缀统一查问题时grep \[abc123def456\] catalina.out即可定位全链路。5.3 用JasperReports替换PrintUtil.java生成专业报表的最小改造PrintUtil.java用 iText 1.3 画表格样式简陋且不支持分页。换成 JasperReports 只需下载jasperreports-1.3.3.jar兼容 JDK 1.4.2放入WebRoot\WEB-INF\lib\将report.jrxml用 iReport 2.0.5 设计编译为report.jasper放在WebRoot\reports\修改SaleAction.java中打印逻辑JasperReport jasperReport JasperCompileManager.compileReport( getServletContext().getRealPath(/reports/report.jrxml)); MapString, Object params new HashMapString, Object(); params.put(saleId, saleId); JasperPrint jasperPrint JasperFillManager.fillReport( jasperReport, params, DBUtil.getConnection()); JasperExportManager.exportReportToPdfFile(jasperPrint, getServletContext().getRealPath(/reports/sale_ saleId .pdf));提示JasperReports 1.3.3是最后一个支持 JDK 1.4 的版本更高版本会报UnsupportedClassVersionError。6. 我的血泪经验为什么坚持让学生手敲ResultSet而不用 ORM去年帮某地市级供销社做系统迁移他们原有 SQL Server 2000 系统用了这套代码的变体。当要对接新上线的省级农产品溯源平台时对方要求提供XML格式接口字段必须含productCode、batchNo、produceDate三字段。团队第一反应是“重写 Spring Boot 接口”但我坚持用原系统改造——因为ProductDAO.java里getProductList()方法返回的是ArrayListProduct而Product类每个字段都有getXXX()方法只需加一个toXML()方法public String toXML() { StringBuilder sb new StringBuilder(); sb.append(product); sb.append(productCode).append(this.productCode).append(/productCode); sb.append(batchNo).append(this.batchNo).append(/batchNo); sb.append(produceDate).append( new SimpleDateFormat(yyyy-MM-dd).format(this.produceDate) ).append(/produceDate); sb.append(/product); return sb.toString(); }然后在ProductAction.java中response.setContentType(text/xml;charsetUTF-8); PrintWriter out response.getWriter(); out.print(?xml version1.0 encodingUTF-8?); out.print(products); for (Product p : productList) { out.print(p.toXML()); } out.print(/products);全程 2 小时搞定零依赖、零配置、零学习成本。而如果当初用了 Hibernate光是配置XmlRootElement和XmlElement注解就得查半天文档更别说Date类型序列化时区问题。这就是手写 JDBC 的力量它不炫技但每个rs.getString(name)都在告诉你数据从哪来、到哪去、怎么变形。当你面对一个连curl都没装的生产服务器或者一个要求“明天上午必须上线”的乡镇客户时这种确定性比任何框架都珍贵。现在我带新人还是会让他们先删掉hibernate.cfg.xml打开DBUtil.java一行行读conn.prepareStatement()怎么写、rs.next()怎么循环、ps.setTimestamp()怎么处理时区。不是守旧是确保他们知道所谓“高级框架”不过是把这几行代码封装了 100 遍。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑