资讯动态

基于Spring Boot的宠物医院管理系统毕业设计:从技术选型到项目评审全流程解析

发布时间:2026/10/27 7:47:45 来源:尧图企业网站定制
简介这套基于SpringBoot与Vue的宠物医院管理系统毕业设计源码适合正在准备毕业设计、课程设计或工程实训的Java学习者也适合作为初期项目立项参考。项目采用前后端分离架构后端用SpringBoot提供接口服务前端用Vue构建交互页面并配套MySQL5.7的SQL脚本导入后即可运行调试技术路线清晰、易上手。资源压缩包共含1167个文件其中包含211个Vue页面文件、127个Java后端类、170个XML配置文件、151个JavaScript逻辑文件以及大量CSS样式、图片与少量JAR依赖包整体仅34.89MB下载和部署都非常方便。目前已有1753人学习系统具备较高的参考价值和二次开发空间可帮助读者理解前后端交互、权限管理、数据表设计等常见业务模块的实现方式。压缩包内还提供了一键运行脚本、Maven配置、Tomcat运行环境说明以及后台/前台访问路径省去环境搭建的摸索时间无论是用于完成毕设答辩、课程作业还是在此基础上扩展新功能都能获得一套完整可落地的项目起点。1. 基于springboot的宠物医院管理系统毕业设计到底该怎么下手宠物医院管理系统是 Java 毕业设计里典型的 Spring Boot 业务系统前台要挂号后台要管病历、药房和收费业务复杂度刚好卡在“能写清”和“有深度”的分界线上。我见到“java毕设-基于springboot的宠物医院管理系统.zip”这类标题时第一反应是拿到压缩包本身不难难的是在答辩时讲清楚核心表怎么设计、挂号收费这个流程怎么用事务保证一致性。下面按我自己实际做过的一版来拆从技术选型、数据库设计到后端实现与常见翻车点最后给出一份可以直接照做的自测用例和验收清单。适合 Java 后端方向、想用 Spring Boot 快速跑通毕业设计的同学。2. 选型与Spring Boot项目结构为什么这套组合能在一周内跑通2.1 技术选型Spring Boot MyBatis Plus MySQL Vue各司其职写管理系统第一件事不是写代码而是把技术栈定下来。我常用的组合是 Spring Boot MyBatis Plus MySQL 5.7/8.0 Vue 2/3。Spring Boot 负责把项目跑起来MyBatis Plus 负责把 Java 实体类转成数据库操作MySQL 存业务数据前端页面打包后直接放进 Spring Boot 的 static 目录这样整个项目交付时就是一个 zip 压缩包老师拿到解压、改一下数据库密码、启动就能看页面。为什么不用原生 MyBatis因为毕业设计的时间太紧了。MyBatis Plus 根据 Java 实体类生成创建表的 SQL 语句还能通过 BaseMapper 直接提供单表 CRUD 能力。你在 entity 里写一个 Pet 实体对应数据库 pet 表Mapper 接口继承 BaseMapper 增删改查全部免写 XML。省下来的时间全部花在挂号、收费这种真正需要手动写 SQL 的业务上性价比非常高。提示Spring Boot 版本不要盲目追新。JDK 8 环境用 Spring Boot 2.xJDK 17 以上才考虑 Spring Boot 3.x。我见过不少同学项目模板下载的是 3.x本机 JDK 还是 1.8启动直接报 UnsupportedClassVersionError这种环境问题在答辩现场非常尴尬。pom.xml 的关键依赖先贴出来parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这段依赖里parent 统一管理所有 Spring Boot 组件的版本避免自己去对齐版本号mybatis-plus-boot-starter 会把 MyBatis、分页插件、SQL 解析器一起带进来。两个参数值得注意mybatis-plus-boot-starter 选 3.5.x 这条线跟 Spring Boot 2.x 搭配得最稳MySQL 的 JDBC 驱动从 8.0.31 开始坐标从 mysql-connector-java 改成了 mysql-connector-j如果照着旧教程复制依赖pom.xml 第一行就会飘红。同理驱动类名要用 com.mysql.cj.jdbc.Driver不是旧的 com.mysql.jdbc.Driver后者只在 MySQL 5.x 的老驱动里出现。2.2 项目结构controller/service/mapper/entity 四层怎么分Spring Boot 项目的目录结构没有强制标准但毕业设计要过查重和答辩清晰的分层结构比代码本身更能撑场面。我习惯按 controller / service / mapper / entity 四层拆外层包名用 com.example.pethospital启动类放在最外面保证 SpringBootApplication 默认的包扫描能把所有子包扫到。下面是我常用的一份结构com.example.pethospital ├── PetHospitalApplication.java ├── config │ ├── MybatisPlusConfig.java │ └── WebConfig.java ├── controller │ ├── PetOwnerController.java │ ├── PetController.java │ ├── AppointmentController.java │ └── MedicalRecordController.java ├── service │ ├── AppointmentService.java │ └── impl │ └── AppointmentServiceImpl.java ├── mapper │ ├── PetOwnerMapper.java │ ├── PetMapper.java │ └── AppointmentMapper.java ├── entity │ ├── PetOwner.java │ ├── Pet.java │ └── Appointment.java ├── common │ ├── Result.java │ └── BusinessException.java └── resources ├── application.yml └── mapper这个结构里controller 只负责接收参数和返回 Resultservice 放业务逻辑mapper 负责和数据库交互entity 里的字段与表字段一一对应。common 包里放统一返回体和自定义异常写毕设时最容易被忽略但答辩时老师一定会问“你们接口返回格式统一吗”有 Result 和 BusinessException 就能答得漂亮。resources/mapper 目录放手写 SQL 的 XML 文件注意要让 mapper-locations 指到这个位置下面 application.yml 里的配置是关键server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_hospital?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.pethospital.entity configuration: map-underscore-to-camel-case: true这里的三个参数建议直接照抄serverTimezoneAsia/Shanghai 是解决数据库时间差 8 小时问题的关键map-underscore-to-camel-case 开启后pet_name 会自动映射到实体的 petName 字段省掉一整套手写 resultMap 的工作type-aliases-package 让 XML 里写实体别名时可以不用带包全名。如果前端用的 Vue打包成 dist 目录以后直接拷到 src/main/resources/static 下同一个 Java 进程就能同时提供页面和接口这也是把 vue 打包产物放进 Spring Boot 的常见做法部署时只需要跑一个 8080 端口答辩换机器演示时少踩很多环境坑。3. Spring Boot 数据库设计与建表SQL一张宠物档案表把业务串起来3.1 Spring Boot 项目里的核心表设计宠物主人、宠物档案、挂号单、诊疗记录写管理系统最怕表建多了自己都理不清。宠物医院管理系统从业务上讲核心是“谁带了哪只宠物来看病、医生做了什么诊断、开了什么药、收了多少钱”所以我把表收敛成 6 张宠物主人表 pet_owner、宠物档案表 pet、挂号单表 appointment、诊疗记录表 medical_record、药品表 drug、处方明细表 prescription。处方明细是诊疗记录和药品之间的关联表用来记录一次诊疗开了哪些药、各多少。这 6 张表的关系可以一句话概括主人 1 对多宠物宠物 1 对多挂号单挂号单 1 对 1 诊疗记录诊疗记录 1 对多处方明细处方明细多对 1 药品。核心字段先列出来设计表的时候对着看表核心字段说明pet_ownerid, name, phone, address宠物主人phone 可加唯一索引petid, owner_id, name, type, gender, birthday宠物档案owner_id 指向 pet_owner.idappointmentid, pet_id, doctor, status, create_time挂号单status 标记就诊状态medical_recordid, appointment_id, symptom, diagnosis诊疗记录与挂号单一对一drugid, name, stock, price, unit药品库存与售价prescriptionid, record_id, drug_id, num处方明细记录开药数量这里最需要注意的是 pet 表。很多毕设会把“宠物”和“主人”堆在同一张表里或者用 owner_name 这种冗余字段看起来简单但一旦要按“某个主人的全部宠物”查病历就会很别扭。我的经验是 pet 表必须单独建owner_id 做成普通索引因为绝大多数查询都是“ownerId 查宠物列表”“petId 查病历”。宠物种类不要用字符串写死“猫/狗/仓鼠”存 type 字段前端用字典翻译后面加“兔子”不用改表结构。3.2 建表SQL与实体类生成字段类型、状态字段、默认值字段类型是另一个容易翻车的地方。金额一律用 decimal(10,2)别用 float 和 double性别、状态这类枚举值用 tinyint创建时间用 datetime配合 DEFAULT CURRENT_TIMESTAMP代码里不用手动赋值。下面以 pet 和 appointment 两张核心表为例CREATE TABLE pet ( id bigint NOT NULL AUTO_INCREMENT, owner_id bigint NOT NULL COMMENT 主人ID, name varchar(50) NOT NULL COMMENT 宠物名称, type varchar(20) DEFAULT NULL COMMENT 物种猫/狗/其他, gender tinyint DEFAULT 0 COMMENT 0未知 1公 2母, birthday date DEFAULT NULL COMMENT 出生日期, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner_id (owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物档案表; CREATE TABLE appointment ( id bigint NOT NULL AUTO_INCREMENT, pet_id bigint NOT NULL COMMENT 宠物ID, doctor varchar(50) DEFAULT NULL COMMENT 接诊医生, status tinyint DEFAULT 0 COMMENT 0待就诊 1就诊中 2已完成 3已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_pet_id (pet_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT挂号单表;状态字段的注释特别重要。status 这种字段是管理系统的“黑匣子”只存 0、1、2、3不写注释的话三个月后你自己都分不清 1 和 2 谁是谁。owner_id 和 pet_id 一定要建普通索引数据量小的时候感觉不出来答辩演示时如果老师现场导入几千条测试数据没索引的联表查询会明显变慢。字符集统一用 utf8mb4不要用 utf8否则宠物名字里出现 Emoji 表情会直接报 Incorrect string value 错误这是一个很小但常见的坑。表建完之后用 MyBatis Plus 生成实体类有两种做法手动写 entity或者用 mybatis-plus-generator 自动生成。毕设项目我建议手动写因为表就 6 张自动生成出来的代码带一堆模板注释查重时反而麻烦。实体类里加上 TableName(pet)、TableId(type IdType.AUTO) 两个注解字段名用驼峰和表字段的下划线对应好Data TableName(pet) public class Pet { TableId(type IdType.AUTO) private Long id; private Long ownerId; private String name; private String type; private Integer gender; private LocalDate birthday; private LocalDateTime createTime; }逻辑说明Data 由 Lombok 提供自动生成 getter/setterTableName 把实体类和 pet 表绑定TableId(type IdType.AUTO) 声明主键自增。createTime 用 LocalDateTime配合 MyBatis Plus 的 map-underscore-to-camel-casecreate_time 字段自动映射到 createTime不需要写 INSERT 语句。gender、status 用 Integer这样能存 null如果写 int数据库 NULL 回填时会报错。4. Spring Boot 后端核心流程挂号→诊疗→开药三张表的联动怎么实现4.1 挂号接口Controller 只收参数Service 里写业务后端代码我从来不在 Controller 里写业务逻辑。一个合格的 Spring Boot 接口应该长这样Controller 接收参数、校验参数、调用 ServiceService 里处理业务Mapper 只做数据库操作。以挂号接口为例前端传过来宠物 ID 和医生姓名后端要做三件事校验宠物存在、创建挂号单、返回挂号单 ID。下面是 Controller 和 Service 的代码RestController RequestMapping(/api/appointment) public class AppointmentController { Resource private AppointmentService appointmentService; PostMapping(/register) public Result register(RequestBody Valid AppointmentDTO dto) { Long appointmentId appointmentService.register(dto); return Result.ok(appointmentId); } }Valid 注解在这里的作用是触发 DTO 里的字段校验比如 petId 不能为空、doctor 不能超过 50 个字符。这样的写法答辩时可以直接说“Controller 层做了参数校验Service 层做了业务校验两层各司其职”比把 if 全堆在方法开头要体面得多。Service 实现类里挂号的核心逻辑是这样Service public class AppointmentServiceImpl extends ServiceImplAppointmentMapper, Appointment implements AppointmentService { Resource private PetMapper petMapper; Override Transactional(rollbackFor Exception.class) public Long register(AppointmentDTO dto) { Pet pet petMapper.selectById(dto.getPetId()); if (pet null) { throw new BusinessException(宠物档案不存在请先建档); } Appointment appointment new Appointment(); appointment.setPetId(pet.getId()); appointment.setDoctor(dto.getDoctor()); appointment.setStatus(0); this.save(appointment); return appointment.getId(); } }逻辑说明先根据 petId 查宠物档案查不到直接抛 BusinessException由全局异常处理器统一转成 JSON 返回而不是让前端收到一堆堆栈信息。然后创建挂号单status 初始化为 0待就诊。这里用 this.save() 而不是 appointmentMapper.insert()是因为 ServiceImpl 里已经继承了 MyBatis Plus 的通用方法代码更短。参数说明里最重要的一个是 Transactional(rollbackFor Exception.class)。Spring 的事务默认只对 RuntimeException 回滚自定义的 BusinessException 如果是继承 RuntimeException 的默认也会回滚但一旦在业务里抛出 IOException 这类受检异常又没有在 rollbackFor 里声明事务就会悄悄提交数据写一半。所以我习惯所有事务方法一律写 rollbackFor Exception.class作为统一标准。4.2 扣药品库存与收费记录并发下避免库存扣成负数诊疗结束后开药涉及两张表处方明细 prescription 和药品表 drug。每开一种药要在 prescription 插入一条记录同时把 drug 表的 stock 减掉对应数量。最容易翻车的是“先查库存再减库存”的写法——两个窗口同时挂号时库存会被减成负数。正确做法是把判断和扣减放在同一条 UPDATE 语句里Mapper public interface DrugMapper extends BaseMapperDrug { Update(UPDATE drug SET stock stock - #{num} WHERE id #{drugId} AND stock #{num}) int deductStock(Param(drugId) Long drugId, Param(num) Integer num); }这段 SQL 的含义是只有当当前库存大于等于需要扣减的数量时才执行扣减并且把 stock 字段原地减掉。返回的 int 代表影响行数业务里拿到返回值后判断影响行数是 1 说明扣减成功是 0 说明库存不足或者药品不存在直接抛 BusinessException 让前台提示。Service 里的调用方式大致是这样的Override Transactional(rollbackFor Exception.class) public void createPrescription(MedicalRecordDTO dto) { long total 0; for (PrescriptionItem item : dto.getItems()) { int rows drugMapper.deductStock(item.getDrugId(), item.getNum()); if (rows 0) { throw new BusinessException(药品库存不足 item.getDrugId()); } Prescription prescription new Prescription(); prescription.setRecordId(dto.getRecordId()); prescription.setDrugId(item.getDrugId()); prescription.setNum(item.getNum()); prescriptionMapper.insert(prescription); total item.getNum() * item.getPrice(); } // 这里继续写收费单total 用 BigDecimal 累计 }这里有两个容易忽略的点。第一deductStock 和 insert 必须放在同一个事务里否则库存扣了、处方没写上数据就不一致第二循环里如果第 3 种药库存不足抛了异常前面两种药的库存会回滚因为外围有 Transactional。收费金额的累计我用的是 long 换算成分为单位或者用 BigDecimal就是不用 doubledouble 在 0.10.2 这种场景会丢精度答辩时现场算一笔药品费用就会露馅。挂号列表的分页接口也建议提前准备好。核心是 QueryWrapper 的条件组装比如按主人手机号过滤时先查 ownerId 列表再用 in 条件查挂号单。分页参数 current 和 size 由前端传默认 current1、size10size 超过 100 要拦截防止有人一次拉全表。这些边界条件写出来答辩时就能说“我的分页接口支持条件组合、参数校验”而不是只说一句“我用了 MyBatis Plus 分页”。5. Spring Boot 毕设避坑这五个问题让宠物医院系统当场翻车这一章列的是我在本地跑项目和答辩现场见到的高频错误每一条都按现象、原因、解决三步写清楚你可以直接对照排查自己的项目。5.1 Mapper 接口扫描不到Spring Boot 启动报 NoSuchBeanDefinitionException现象项目能编译通过但启动时 Spring 容器报错说找不到 PetMapper 类型的 Bean。原因启动类上没有加 MapperScan或者启动类所在包比 mapper 包高一级默认扫描范围没覆盖到 mapper 接口。Spring Boot 默认只扫描启动类所在包及子包com.example.pethospital 启动类能扫到 com.example.pethospital.mapper但如果启动类放到了 com.example 下就看运气了。解决在启动类上直接加 MapperScan(com.example.pethospital.mapper)或者在每个 Mapper 接口上加 Mapper 注解。二选一就行两个都加也不会报错但没有任何必要。我个人习惯用 MapperScan因为它是集中管理新加 Mapper 不用再补注解。5.2 数据库时间差了 8 小时现象前端页面上显示的挂号时间是上午 10 点数据库里存的是凌晨 2 点或者反过来。原因JDBC URL 里没有配置 serverTimezoneMySQL 使用的时区与服务器本地时区不一致。本地开发一般没事一旦换一台不同时区的机器演示问题立刻暴露。解决JDBC URL 里带上 serverTimezoneAsia/Shanghai同时在 application.yml 里把 Jackson 的时间格式也固定下来spring.jackson.date-formatyyyy-MM-dd HH:mm:ssspring.jackson.time-zoneAsia/Shanghai。前者管数据库读写后者管 JSON 序列化两个都要设少一个都会出现“库里对、接口错”的诡异情况。5.3 宠物和主人相互引用JSON 序列化死循环现象Postman 里请求宠物列表接口浏览器直接转圈后端日志疯狂刷 com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion。原因Pet 实体里有 Owner 对象Owner 实体里又有 List Jackson 在序列化时 A 套 B、B 又套 A循环递归直到栈溢出。这个在实体层把关联对象写得太满的项目里很常见。解决我一般只保留单向关联Pet 实体里只有 ownerId 长整型字段没有 Owner 对象查询宠物列表需要主人姓名时在 Service 层用 ownerId 查出 Owner 再手动组装成 VO。如果确实要双向 JSON至少在其中一侧加 JsonIgnore比如 Owner 的 pets 列表上。5.4 MyBatis Plus 分页不生效接口返回全部数据现象调用 selectPage 方法返回的 records 是全部数据total 永远为 0页码参数像没传一样。原因MyBatis Plus 的物理分页依赖分页插件只引入 mybatis-plus-boot-starter 是不够的没有注册 PaginationInnerInterceptor 就不会在 SQL 后面拼 LIMIT。解决在 config 包里加一个配置类注册 MybatisPlusInterceptor。代码要单独写进一个类里Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }逻辑说明MybatisPlusInterceptor 是插件容器addInnerInterceptor 往里加分页插件DbType.MYSQL 告诉它按 MySQL 的 LIMIT 语法拼接。写完这个类selectPage 才会真正执行带 LIMIT 的 SQL。如果项目里还用了逻辑删除、乐观锁同样在这个容器里加对应插件顺序上分页建议放最后。5.5 在 try-catch 里调同类方法事务失效数据照写现象挂号方法里 try-catch 包住了一个开药方法开药方法抛出异常后挂号单还是入库了。原因Spring 的事务是通过 AOP 代理实现的同类里的 this.method() 调用不会经过代理对象所以事务注解完全没有生效另外 try-catch 把异常吞掉了事务管理根本不知道有异常发生。解决事务方法放到独立的 Service 里由另一个 Service 调用或者用 Autowired 注入自己的代理对象再用代理对象调用异常一律向外抛不要 catch 后打印一行日志就继续往下走。毕设场景里最稳的做法是把开药和收费串到一个 Service 方法里整个流程一个事务。6. 从能跑到能过审自测用例、验收清单和答辩前的最后一小时答辩前我最喜欢做的事不是反复点页面而是拿 Postman 把一个完整业务流从头跑到尾。这个流程能跑通系统的主干逻辑就没有大问题。下面是一份可以直接照抄的自测用例表顺序不能乱后一步依赖前一步返回的 ID。步骤操作预期结果1新增宠物主人返回 ownerId2用 ownerId 新增宠物返回 petId宠物列表出现一条记录3用 petId 挂号返回 appointmentId状态为 04写诊疗记录病历表新增一条记录挂号状态变为 15开药并扣库存药品库存减少处方明细新增一条6再次开同一种药数量超过库存提示库存不足前一步操作回滚第 6 步是老师最爱现场试的。你可以主动演示“库存不足时库存不会被扣成负数”这比讲十页 PPT 都管用。压测不用做但事务回滚一定要验证故意让开药失败然后查挂号单和药品库存确认数据恢复到操作前。交付 zip 之前再把这几样东西塞进压缩包建表 SQL 脚本、README写清 JDK/MySQL 版本、启动步骤、默认账号密码、application.yml 的配置说明。README 里写清楚“先建库、再改密码、后启动”三个步骤很多本地的项目拿到手跑不起来八成是卡在数据库初始化这一步。最后说一个我的教训前后端分离的项目不要在答辩时开两个端口演示。Vue 打包后放进 Spring Boot 的 static 目录只用 8080 一个端口换机器、连投影、断网重连都稳定得多。我当时因为前端开 8080、后端开 9090跨域加 session 问题折腾了一晚上后来干脆把 dist 塞进 Spring Boot世界清净了。希望这篇能让你在答辩前少走这一段弯路也希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑