多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java宠物管理系统:JDBC事务与SQL注入防护实战

Java宠物管理系统:JDBC事务与SQL注入防护实战 简介本资源是一套完整的Java毕业设计项目——宠物管理系统面向计算机专业本科生及Java初学者解决课程设计、毕设选题与Web开发实践中的典型需求。压缩包共14个文件含4个教学视频覆盖前后台部署、模块实现与数据库创建、3张功能界面截图、3份Word文档含任务书、中期检查表与完整论文、1个PPT答辩稿、1个SQL数据库脚本、1个源码ZIP包及1个说明TXT总大小132.62MB内容组织清晰便于分阶段学习与复现。已有267人下载学习资源提供从环境搭建、前后端功能演示到数据库设计的全流程支撑尤其包含后台订单/商品/会员管理等核心模块的代码实现与5张关键数据表结构说明辅以实操录屏与图文资料显著降低独立开发门槛助力快速掌握SSM或Spring Boot框架下的中小型管理系统开发全链路。1. 为什么一个“宠物管理系统”能成为Java初学者的通关副本不是所有Java课程设计都值得你花48小时调试——但这个基于Java的宠物管理系统是例外。它不靠炫技没用Spring Cloud、没上K8s而是用最朴素的SwingJDBCMySQL组合把增删改查、数据校验、事务控制、文件导出这些企业开发里天天打交道的“脏活累活”全塞进一个不到2000行的可运行工程里。我带过6届校招实习生发现凡是完整跑通这个系统、自己重写过DAO层、手动调过SQL注入防护点的人面试时被问到“事务隔离级别怎么设”“PreparedStatement为什么比Statement安全”基本能当场画出执行流程图。它不是玩具项目而是把《Java核心技术卷I》第12章JDBC、《Effective Java》第57条资源释放、《数据库系统概念》第9章并发控制全揉进一个宠物领养登记表里的实战沙盒。适合刚学完集合、IO、多线程正卡在“写了10个HelloWorld却不敢碰真实业务逻辑”的人——别急着冲Spring Boot先让这个.rar里的源码教会你什么叫“代码能跑数据不丢用户不骂”。2. 从解压到启动四步走通本地环境链路这个.rar包表面看是“源码视频数据库论文”实际核心只有三件套src/下的Java源码、db/pet_manager.sql建库脚本、lib/里那4个JAR包mysql-connector-java-8.0.28.jar、commons-dbutils-1.7.jar、junit-4.13.2.jar、swing-layout-1.0.4.jar。视频和论文是锦上添花但启动失败90%的原因都在这三件套的版本咬合上。下面按真实踩坑顺序拆解。2.1 解压后第一件事检查JDK版本与编译目标提示别急着双击IDEA导入先看src/目录下有没有.classpath或pom.xml——没有说明这是纯Java SE项目不是Maven工程。直接用记事本打开src/com/pet/dao/DBUtil.java找到第15行private static final String URL jdbc:mysql://localhost:3306/petdb?useSSLfalseserverTimezoneGMT%2B8;这行暴露了关键信息它硬编码了MySQL连接地址且指定了serverTimezoneGMT%2B8。这意味着你的MySQL服务必须开启时区支持且JDK版本不能低于8u202否则GMT%2B8解析会抛SQLException: Unknown system variable time_zone。验证方式命令行输入java -version mysql --version要求JDK ≥ 1.8.0_202MySQL ≥ 5.7.208.0更稳。若JDK是17需额外在JDBC URL末尾加allowPublicKeyRetrievaltrueuseSSLfalse否则连接时会卡在Public Key Retrieval is not allowed错误。2.2 数据库初始化别信“一键导入”手动执行才可控包里db/pet_manager.sql不是标准SQL dump而是带CREATE DATABASE IF NOT EXISTS petdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;的初始化脚本。很多新手双击Navicat“执行SQL文件”失败原因是Navicat默认用utf8字符集解析而脚本里utf8mb4需要显式声明。正确做法三步保命启动MySQL服务用命令行登录mysql -u root -p手动创建库并指定字符集CREATE DATABASE petdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE petdb;逐行粘贴pet_manager.sql内容注意删掉开头的DROP DATABASE IF EXISTS petdb;——这句在生产环境是核弹本地测试也容易误删其他库。重点检查三张表pet_info主键pet_id必须是BIGINT AUTO_INCREMENT不是INT宠物编号可能超21亿owner_infophone字段用VARCHAR(11)而非CHAR(11)留出手机号前缀扩展空间adoption_recordadopt_time字段类型必须是DATETIME不是TIMESTAMP后者受MySQL时区设置影响会导致查询结果时间偏移2.3 源码编译绕过IDE自动配置陷阱IntelliJ IDEA导入时默认会把src/设为Sources Root但lib/里的JAR包不会自动加入Classpath——这是新手编译报ClassNotFoundException: org.apache.commons.dbutils.QueryRunner的头号原因。手动添加依赖以IDEA为例右键项目 →Open Module Settings→Modules→Dependencies点→JARs or directories→ 选中lib/目录下全部4个JAR关键一步勾选Export复选框否则运行时仍找不到类在Project Settings→Project里确认Project SDK指向你验证过的JDK 1.8Language level设为8 - Lambdas, type annotations etc.源码里用了Lambda表达式如OwnerDao.java第42行的list.stream().filter(...)编译成功标志com.pet.ui.MainFrame.java里右键Run MainFrame.main()弹出窗口标题为“宠物管理系统V1.0”——不是黑屏不是报错是真·图形界面。3. 核心模块拆解DAO层如何用JDBC写出“防翻车”代码这个系统最值得细读的是com.pet.dao包。它没用MyBatis或Hibernate而是用Apache Commons DbUtils手写DAO好处是每行SQL、每个参数绑定、每次连接关闭都赤裸裸摆在你眼前。我们以PetDao.java的updatePet()方法为例解剖它如何规避三大经典翻车点。3.1 事务控制为什么不用Connection.setAutoCommit(false)看updatePet()第28行QueryRunner qr new QueryRunner(); // 注意没传入Connection int rows qr.update(conn, sql, pet.getName(), pet.getSpecies(), ... , pet.getPetId());这里qr.update()的第一个参数是conn已开启事务的Connection而不是qr自己管理连接。原因在于QueryRunner默认每次操作新建Connection无法跨DAO方法维持事务。真正的事务控制在com.pet.service.PetService.java的updatePetWithTransaction()里public boolean updatePetWithTransaction(Pet pet) { Connection conn null; try { conn DBUtil.getConnection(); // 从DBUtil获取连接 conn.setAutoCommit(false); // 关闭自动提交 petDao.updatePet(conn, pet); // 传入conn确保同一事务 ownerDao.updateOwner(conn, pet.getOwnerId()); // 同一conn跨DAO conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} // 必须rollback } return false; } finally { DBUtil.close(conn); // 关键conn必须在这里close不能在DAO里close } }参数说明conn.setAutoCommit(false)告诉MySQL“接下来所有操作算一个原子单元”petDao.updatePet(conn, pet)DAO方法签名必须接收Connection参数否则事务失效DBUtil.close(conn)放在finally块且只close一次——若在DAO里closeservice层再用conn会抛SQLException: Connection is closed3.2 SQL注入防护PreparedStatement的“玄学”占位符PetDao.java第65行SQL字符串private static final String UPDATE_SQL UPDATE pet_info SET name?, species?, age?, health_status?, owner_id? WHERE pet_id?;注意所有值都用?绝不拼接字符串。比如错误写法// ❌ 千万别这么写 String sql UPDATE pet_info SET name pet.getName() WHERE pet_id pet.getPetId();这种写法遇到宠物名含单引号如OReilly直接SQL语法错误更危险的是若pet.getName()被恶意构造为 OR 11整张表数据会被清空。正确绑定参数逻辑qr.update(conn, UPDATE_SQL, pet.getName(), // ?1 → name pet.getSpecies(), // ?2 → species pet.getAge(), // ?3 → age pet.getHealthStatus(), // ?4 → health_status pet.getOwnerId(), // ?5 → owner_id pet.getPetId() // ?6 → WHERE pet_id );DbUtils会自动调用PreparedStatement.setString(1, pet.getName())等方法底层由JDBC驱动做参数转义彻底隔绝注入风险。3.3 空值处理ResultSet的“后悔药”机制PetDao.java的findPetById()方法里rs.getString(name)可能返回null但UI层MainFrame.java第189行直接petNameTextField.setText(pet.getName())——如果pet.getName()是null文本框会显示null字符串而非空白。血泪经验在DAO层就做空值兜底Pet pet new Pet(); pet.setName(rs.getString(name) null ? : rs.getString(name)); pet.setSpecies(rs.getString(species) null ? 未知 : rs.getString(species)); // ... 其他字段同理或者更优雅地在Pet实体类的getter里统一处理public String getName() { return name null ? : name; }这样UI层无需判空降低耦合度。但要注意数据库字段定义必须允许NULLname VARCHAR(50) NULL否则rs.getString(name)永远不为null只是空字符串。4. 避坑指南那些让开发者凌晨三点还在抓头发的5个致命细节这个系统看似简单但每个模块都埋着教科书级的“反模式”陷阱。以下是我在带学员实操时记录的5个高频翻车点按现象→原因→解决结构整理拒绝模糊描述。4.1 现象启动后界面文字全是方框□□□原因Swing组件默认使用系统字体Windows下是微软雅黑但Linux/macOS无此字体且MainFrame.java第32行setFont(new Font(微软雅黑, Font.PLAIN, 14))硬编码了字体名。解决替换为跨平台字体修改MainFrame.java第32行// ✅ 改为 setFont(new Font(Font.DIALOG, Font.PLAIN, 14)); // DIALOG是Swing推荐的跨平台字体 // 或更稳妥的方案检测系统 String fontName System.getProperty(os.name).toLowerCase().contains(win) ? 微软雅黑 : SansSerif; setFont(new Font(fontName, Font.PLAIN, 14));4.2 现象添加新宠物后列表不刷新重启程序才显示原因MainFrame.java的addBtn.addActionListener里调用petService.addPet(pet)成功后没触发tableModel.fireTableDataChanged()刷新JTable。解决在addBtn监听器末尾petService.addPet(pet)之后添加// ✅ 补充刷新逻辑 petTableModel.setPetList(petService.findAllPets()); // 重新加载数据 petTableModel.fireTableDataChanged(); // 通知表格重绘4.3 现象导出Excel功能点击无反应控制台无报错原因com.pet.util.ExcelExport.java依赖org.apache.poi.ss.usermodel.Workbook但lib/目录下只有poi-3.17.jar缺少poi-ooxml-3.17.jar处理.xlsx必需。解决下载poi-ooxml-3.17.jar注意版本必须与poi-3.17.jar一致放入lib/目录并在IDEA中重新添加为依赖。验证方式在ExcelExport.java里CtrlClickXSSFWorkbook能跳转到源码即成功。4.4 现象修改宠物年龄为负数系统接受并存入数据库原因PetService.java的updatePet()方法里对pet.getAge()只做了if (pet.getAge() 0)判断但没抛异常或返回false导致校验形同虚设。解决强化校验逻辑修改PetService.java第112行// ✅ 增加明确的业务规则拦截 if (pet.getAge() 0 || pet.getAge() 50) { // 宠物年龄合理范围0-50 throw new IllegalArgumentException(宠物年龄必须在0-50之间); }4.5 现象删除宠物后关联的领养记录未同步删除数据库出现孤儿记录原因PetDao.java的deletePet()只执行DELETE FROM pet_info WHERE pet_id?没处理外键约束。而adoption_record表的pet_id字段是外键但建表时没加ON DELETE CASCADE。解决两种方案任选其一方案A推荐修改pet_manager.sql在创建adoption_record表时添加级联CREATE TABLE adoption_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, pet_id BIGINT NOT NULL, owner_id BIGINT NOT NULL, adopt_time DATETIME NOT NULL, FOREIGN KEY (pet_id) REFERENCES pet_info(pet_id) ON DELETE CASCADE, FOREIGN KEY (owner_id) REFERENCES owner_info(owner_id) );方案B代码层在PetService.deletePet()里先调用adoptionRecordDao.deleteByPetId(petId)再删宠物。5. 让系统真正“活”起来三个进阶改造技巧跑通基础功能只是起点。我一般会让学员用以下三个技巧把静态Demo变成有业务呼吸感的系统——它们不增加复杂度但直击企业开发痛点。5.1 把“数据库密码”从代码里抠出来配置文件驱动现在DBUtil.java第12行写着private static final String PASSWORD root; // ❌ 明文密码这违反安全红线。改造方案用src/config/db.properties替代硬编码。步骤在src/下新建config/db.propertiesjdbc.urljdbc:mysql://localhost:3306/petdb?useSSLfalseserverTimezoneGMT%2B8 jdbc.usernameroot jdbc.passwordyour_secure_password jdbc.drivercom.mysql.cj.jdbc.Driver修改DBUtil.java用Properties加载private static Properties props; static { try (InputStream is DBUtil.class.getClassLoader() .getResourceAsStream(config/db.properties)) { props new Properties(); props.load(is); } catch (IOException e) { throw new RuntimeException(加载数据库配置失败, e); } } // getConneciton()方法里 String url props.getProperty(jdbc.url); String username props.getProperty(jdbc.username); String password props.getProperty(jdbc.password);价值部署到测试环境时只需换db.properties不用改一行Java代码。5.2 给搜索加“模糊匹配”LIKE语句的性能平衡术当前PetDao.findPetsByName()用WHERE name ?精确匹配用户搜“金毛”找不到“金毛寻回犬”。改成模糊搜索很简单// ✅ 修改SQL private static final String FIND_BY_NAME_SQL SELECT * FROM pet_info WHERE name LIKE ?; // 调用时 qr.query(conn, FIND_BY_NAME_SQL, handler, % keyword %);但注意LIKE %keyword%会导致索引失效。折中方案是限制前缀匹配// ✅ 更优写法用户输入“金毛”时搜“金毛%” qr.query(conn, SELECT * FROM pet_info WHERE name LIKE ?, handler, keyword %);这样name字段的B树索引仍生效响应速度从秒级降到毫秒级。5.3 日志埋点用SLF4J替代System.out.printlnPetService.java里大量System.out.println(更新宠物成功)线上环境根本没法追踪。换成SLF4J只需两步lib/里加slf4j-api-1.7.32.jar和slf4j-simple-1.7.32.jar在PetService.java顶部import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger log LoggerFactory.getLogger(PetService.class);替换所有System.out.println// ✅ 替换后 log.info(开始更新宠物ID{}的信息, pet.getPetId()); log.debug(更新参数name{}, age{}, pet.getName(), pet.getAge());效果日志自动按级别过滤DEBUG只在开发环境输出且能输出时间戳、线程名、类名排查问题时不再靠grep大海捞针。最后说句实在话我当年也是从这个.rar包开始第一次知道conn.rollback()要写在catch里第一次明白PreparedStatement不是语法糖而是安全底线第一次因为没关ResultSet导致MySQL连接数爆满被运维打电话骂醒。它不酷但足够真实——就像所有靠谱的工程师都是从修好一个宠物系统的增删改查开始的。希望帮到你。本文还有配套的精品资源点击获取
返回列表