☰
Java宠物管理系统:JDBC事务与SQL注入防护实战
2026/10/9 12:56:28 网站建设 项目流程

简介:本资源是一套完整的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),而是用最朴素的Swing+JDBC+MySQL组合,把增删改查、数据校验、事务控制、文件导出这些企业开发里天天打交道的“脏活累活”,全塞进一个不到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?useSSL=false&serverTimezone=GMT%2B8";

这行暴露了关键信息:它硬编码了MySQL连接地址,且指定了serverTimezone=GMT%2B8。这意味着你的MySQL服务必须开启时区支持,且JDK版本不能低于8u202(否则GMT%2B8解析会抛SQLException: Unknown system variable 'time_zone')。

验证方式:命令行输入

java -version mysql --version

要求:JDK ≥ 1.8.0_202,MySQL ≥ 5.7.20(8.0更稳)。若JDK是17+,需额外在JDBC URL末尾加&allowPublicKeyRetrieval=true&useSSL=false,否则连接时会卡在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需要显式声明。

正确做法(三步保命):

  1. 启动MySQL服务,用命令行登录:
    mysql -u root -p
  2. 手动创建库并指定字符集:
    CREATE DATABASE petdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE petdb;
  3. 逐行粘贴pet_manager.sql内容(注意删掉开头的DROP DATABASE IF EXISTS petdb;——这句在生产环境是核弹,本地测试也容易误删其他库)。

重点检查三张表:

  • pet_info:主键pet_id必须是BIGINT AUTO_INCREMENT,不是INT(宠物编号可能超21亿)
  • owner_info:phone字段用VARCHAR(11)而非CHAR(11),留出手机号前缀扩展空间
  • adoption_record:adopt_time字段类型必须是DATETIME,不是TIMESTAMP(后者受MySQL时区设置影响,会导致查询结果时间偏移)

2.3 源码编译:绕过IDE自动配置陷阱

IntelliJ IDEA导入时,默认会把src/设为Sources Root,但lib/里的JAR包不会自动加入Classpath——这是新手编译报ClassNotFoundException: org.apache.commons.dbutils.QueryRunner的头号原因。

手动添加依赖(以IDEA为例):

  1. 右键项目 →Open Module Settings→Modules→Dependencies
  2. 点+→JARs or directories→ 选中lib/目录下全部4个JAR
  3. 关键一步:勾选Export复选框(否则运行时仍找不到类)
  4. 在Project Settings→Project里,确认Project SDK指向你验证过的JDK 1.8+,Language 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里close,service层再用conn会抛SQLException: Connection is closed

3.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();

这种写法遇到宠物名含单引号(如O'Reilly)直接SQL语法错误;更危险的是,若pet.getName()被恶意构造为' OR '1'='1,整张表数据会被清空。

正确绑定参数逻辑:

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层无需判空,降低耦合度。但要注意:数据库字段定义必须允许NULL(name 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里Ctrl+ClickXSSFWorkbook,能跳转到源码即成功。

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替代硬编码。

步骤:

  1. 在src/下新建config/db.properties:
    jdbc.url=jdbc:mysql://localhost:3306/petdb?useSSL=false&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=your_secure_password jdbc.driver=com.mysql.cj.jdbc.Driver
  2. 修改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.println

PetService.java里大量System.out.println("更新宠物成功"),线上环境根本没法追踪。换成SLF4J只需两步:

  1. lib/里加slf4j-api-1.7.32.jar和slf4j-simple-1.7.32.jar
  2. 在PetService.java顶部:
    import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger log = LoggerFactory.getLogger(PetService.class);
  3. 替换所有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连接数爆满被运维打电话骂醒。它不酷,但足够真实——就像所有靠谱的工程师,都是从修好一个宠物系统的增删改查开始的。希望帮到你。

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

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

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

立即咨询