恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Java宠物管理系统实战:Spring Boot+MyBatis-Plus全栈开发指南
首页
资讯中心
/
Java宠物管理系统实战:Spring Boot+MyBatis-Plus全栈开发指南
Java宠物管理系统实战:Spring Boot+MyBatis-Plus全栈开发指南
发布时间:2026/9/2 5:12:26
简介这是一套基于SpringBoot与Vue前后端分离架构实现的Java宠物管理系统面向计算机专业本科生开展毕业设计或课程实践解决宠物主人与管理员对宠物信息、健康档案、服务预约及社区互动的一体化管理需求。资源包共471个文件涵盖154个Java后端业务与实体类、111个Vue组件与页面逻辑、47个PNG界面图标、41个JS交互脚本及35个JPG宠物图片素材辅以XML配置、CSS样式、SQL建表语句等完整呈现典型企业级Web应用的工程结构压缩包大小为10.81MB。已有129人学习下载适合需要参考真实项目分层设计、权限控制实现、文件上传与定时提醒功能集成的学习者。读者可直接导入运行获得含用户角色权限、宠物全生命周期管理、健康提醒机制、服务预约流程及论坛社交模块的可部署系统并通过清晰的目录划分如controller/service/mapper与views/api/store快速理解前后端协作范式。1. 项目概述从零到一构建一个实用的宠物管理系统最近在整理过往项目时翻到了一个挺有意思的练手项目——一个用Java实现的宠物管理系统。这玩意儿乍一听像是学生时代的课程设计但真要把它做得扎实、能用里面涉及的技术点和设计考量其实一点也不少。无论是想巩固Java Web基础的新手还是想找个完整案例来梳理自己技术栈的同行这个项目都挺有嚼头。它本质上是一个典型的信息管理系统核心就是围绕“宠物”这个实体完成信息的增删改查并管理与之相关的客户、医疗服务、商品等业务。麻雀虽小五脏俱全从数据库设计、后端业务逻辑到前端页面交互都能覆盖到。我之所以觉得它值得拿出来聊聊是因为在实现过程中你会遇到很多真实开发中的典型问题比如如何设计一个扩展性好的数据库表结构如何在面向对象的思想下组织你的Service层前后端数据交互用什么格式更合适权限控制怎么做才简单有效这些问题在这个项目里都能找到具体的实践场景。接下来我就结合自己当时的设计与实现把其中的核心思路、关键代码以及踩过的那些坑系统地梳理一遍。2. 系统整体设计与核心思路拆解2.1 需求分析与功能模块规划做任何系统第一步永远是搞清楚要干什么。对于一个宠物管理系统核心用户通常是宠物店的店主或管理员。他们的核心诉求无非是管好客户和他们的宠物记录每次的消费比如洗澡、看病、买粮并且能方便地查询历史记录和统计营收。基于此我们可以梳理出几个核心功能模块宠物信息管理这是系统的基石。需要记录宠物的基本信息名字、品种、年龄、体重等、所属主人客户以及它的健康档案疫苗接种记录、过往病史。客户信息管理客户是服务的对象。需要管理客户的基本联系信息并且要能清晰地看到该客户名下有哪些宠物。服务与商品管理这是产生收入的模块。需要定义店铺提供的服务项目如美容、体检、疫苗和销售的商品如狗粮、玩具并管理其价格。消费记录与订单管理这是业务流转的核心。客户带宠物来消费可能同时购买服务和商品系统需要能创建订单关联宠物、选择的服务和商品并计算总价。系统管理与统计包括用户店员的登录、权限管理以及基于消费数据的简单统计报表比如日/月营收、热门服务排行等。在技术选型上我选择了最经典、也最稳妥的Java Web技术栈Spring Boot MyBatis-Plus MySQL Thymeleaf。选择Spring Boot是因为它能极大简化配置让我们聚焦业务MyBatis-Plus是对MyBatis的增强它的通用Mapper和条件构造器能节省大量枯燥的CRUD代码MySQL是关系型数据库的常青树足以应对这类管理系统的数据存储前端选用Thymeleaf模板引擎而不是前后端分离的Vue/React主要是为了让项目更紧凑学习曲线更平缓所有技术栈都在Java生态内便于理解和部署。2.2 数据库表结构设计详解数据库设计是系统的骨架设计得好后续开发事半功倍。这里遵循基本的数据库范式同时兼顾查询效率。我设计了以下几张核心表pet(宠物表)id: 主键自增。name: 宠物名。type: 宠物类型猫、狗、兔等。breed: 品种金毛、布偶等。birthday: 出生日期用于计算年龄。owner_id: 外键关联client表表示宠物的主人。health_status: 健康状况简述。create_time/update_time: 记录创建和更新时间。client(客户表)id: 主键。name: 客户姓名。phone: 手机号可作为登录账号。address: 地址。emergency_contact: 紧急联系人。service_item(服务项目表) product(商品表)这两张表结构类似存储可供销售的服务和商品的基本信息如name,price,description,status(上架/下架)。核心难点在于order(订单表)的设计。一个订单可能包含多项服务和商品这是一个典型的一对多关系。我采用了主-子表的设计order(订单主表)记录订单整体信息。order_no: 订单号唯一可按规则生成如202405210001。client_id: 客户ID。pet_id: 本次消费关联的宠物ID一次消费可能只针对一只宠物。total_amount: 订单总金额。status: 订单状态待支付、已完成、已取消。payment_method: 支付方式。order_service(订单服务明细表)order_product(订单商品明细表)这两张表结构类似记录订单中包含的具体项。字段包括id,order_id(关联主表),item_id(关联服务或商品表),quantity(数量),price(下单时的单价与主表价格解耦避免后续商品调价影响历史订单)。设计心得将订单明细与商品/服务主表价格解耦是非常重要的。商品price字段变动是常态如果订单明细直接引用商品ID那么历史订单的金额就会跟着变这会造成财务数据混乱。所以在生成订单明细时必须将当时的单价price快照一份存储下来。3. 后端核心业务逻辑实现3.1 项目架构与分层设计我采用了经典的MVC分层架构在Spring Boot中对应如下Controller层接收前端请求进行参数校验调用Service层返回结果JSON或跳转视图。Service层业务逻辑的核心。处理复杂的业务规则事务管理通常在这一层。Mapper层 (DAO层)由MyBatis-Plus的BaseMapper接口和对应的XML文件或注解组成负责与数据库直接交互。Entity层 (Model层)与数据库表一一对应的Java实体类。此外还有DTO(Data Transfer Object)用于前后端数据传输如接收查询条件、返回复杂结果VO(View Object)用于封装返回给前端页面的数据。3.2 关键业务场景代码解析1. 宠物与客户的关联查询这是非常常见的需求查看客户详情时要列出他所有的宠物。在ClientService中我们不会简单地在查询客户后再循环查询宠物表那样会产生N1查询问题。更优的做法是使用MyBatis-Plus的关联查询或者自己编写XML映射。// ClientService.java public ClientVO getClientWithPets(Long clientId) { Client client clientMapper.selectById(clientId); if (client null) { throw new BusinessException(客户不存在); } // 查询该客户的所有宠物 QueryWrapperPet wrapper new QueryWrapper(); wrapper.eq(owner_id, clientId); ListPet petList petMapper.selectList(wrapper); // 组装VO对象返回给前端 ClientVO clientVO new ClientVO(); BeanUtils.copyProperties(client, clientVO); clientVO.setPetList(petList); return clientVO; }2. 创建订单——一个典型的事务操作创建订单是系统最复杂的业务之一它涉及多个步骤必须在一个事务中完成保证原子性。// OrderService.java Transactional(rollbackFor Exception.class) // 声明式事务任何异常都回滚 public String createOrder(OrderCreateDTO createDTO) { // 1. 参数校验略 // 2. 生成订单号使用时间戳随机数或分布式ID生成器 String orderNo generateOrderNo(); // 3. 计算总金额需要查询服务和商品的实时价格 BigDecimal totalAmount BigDecimal.ZERO; ListOrderServiceDetail serviceDetails new ArrayList(); for (OrderServiceDTO s : createDTO.getServices()) { ServiceItem item serviceItemMapper.selectById(s.getItemId()); if (item null || item.getStatus() ! 1) { throw new BusinessException(服务项目不存在或已下架); } BigDecimal itemTotal item.getPrice().multiply(new BigDecimal(s.getQuantity())); totalAmount totalAmount.add(itemTotal); // 构建明细对象快照价格 OrderServiceDetail detail new OrderServiceDetail(); detail.setOrderNo(orderNo); detail.setItemId(item.getId()); detail.setItemName(item.getName()); detail.setQuantity(s.getQuantity()); detail.setPrice(item.getPrice()); // 快照价格 serviceDetails.add(detail); } // ... 同样处理商品 // 4. 保存订单主表 Order order new Order(); order.setOrderNo(orderNo); order.setClientId(createDTO.getClientId()); order.setPetId(createDTO.getPetId()); order.setTotalAmount(totalAmount); order.setStatus(OrderStatusEnum.UNPAID.getCode()); orderMapper.insert(order); // 5. 批量保存订单明细 if (!serviceDetails.isEmpty()) { orderServiceDetailMapper.insertBatch(serviceDetails); // 需要自定义批量插入方法 } // ... 保存商品明细 // 6. 可选扣减库存等操作 return orderNo; }踩坑记录这里最容易出问题的地方是并发。比如商品A库存只剩1件两个用户同时下单购买如果不加锁可能导致超卖。对于简单的管理系统可以在查询商品时使用select ... for update进行行锁或者在更新库存时使用update product set stock stock - 1 where id ? and stock 1这种乐观锁方式。我们这个示例暂时省略了库存字段但实际业务中必须考虑。3.3 使用MyBatis-Plus提升开发效率MyBatis-Plus简称MP是这个项目的“效率神器”。它几乎免去了所有单表的简单CRUD的SQL编写。通用Mapper让你的Mapper接口继承BaseMapperEntity立刻拥有selectById,insert,updateById,deleteById,selectList等方法。条件构造器QueryWrapper和UpdateWrapper让你能用Java Lambda表达式的方式构建查询条件代码更清晰避免SQL注入。// 示例查询今天之后过生日的狗狗 QueryWrapperPet wrapper new QueryWrapper(); wrapper.eq(type, 狗) .apply(DATE_FORMAT(birthday, %m-%d) DATE_FORMAT(NOW(), %m-%d)) // 使用数据库函数 .orderByAsc(birthday); ListPet dogs petMapper.selectList(wrapper);分页插件配置一个分页拦截器PaginationInnerInterceptor就能轻松实现物理分页。// 在Service中 PageOrder page new Page(pageNum, pageSize); // 当前页每页大小 QueryWrapperOrder wrapper new QueryWrapper(); wrapper.eq(client_id, clientId).orderByDesc(create_time); PageOrder orderPage orderMapper.selectPage(page, wrapper); // orderPage.getRecords() 是当前页数据orderPage.getTotal() 是总记录数4. 前端页面与用户交互实现4.1 基于Thymeleaf的模板开发前端没有用复杂的框架而是用Spring Boot默认支持的Thymeleaf。它的好处是能在HTML中直接使用Spring表达式语言与后端Controller无缝集成。Controller传递数据到页面GetMapping(/client/list) public String clientList(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, Model model) { PageClient page new Page(pageNum, pageSize); PageClient clientPage clientService.page(page); model.addAttribute(pageInfo, clientPage); // 将分页数据放入Model return client/list; // 对应 templates/client/list.html }在HTML中接收并渲染数据!-- list.html 片段 -- table tr th:eachclient : ${pageInfo.records} td th:text${client.name}/td td th:text${client.phone}/td td a th:href{/client/edit/{id}(id${client.id})}编辑/a a hrefjavascript:; th:onclickdeleteClient( ${client.id} )删除/a /td /tr /table !-- 分页组件 -- div th:if${pageInfo.pages 1} a th:href{/client/list(pageNum1)}首页/a a th:if${pageInfo.hasPrevious()} th:href{/client/list(pageNum${pageInfo.previous})}上一页/a span th:eachi : ${#numbers.sequence(1, pageInfo.pages)} a th:if${i ! pageInfo.current} th:href{/client/list(pageNum${i})} th:text${i}/a strong th:if${i pageInfo.current} th:text${i}/strong /span a th:if${pageInfo.hasNext()} th:href{/client/list(pageNum${pageInfo.next})}下一页/a a th:href{/client/list(pageNum${pageInfo.pages})}尾页/a /div4.2 利用Bootstrap快速搭建UI为了快速得到一个美观可用的界面我引入了Bootstrap 5的CSS和JS。它提供了现成的网格系统、组件和工具类让非专业前端的后端开发者也能做出像样的页面。布局使用container,row,col-*系列类进行响应式布局。组件大量使用table,form-control,btn,alert,modal等组件来构建表单、按钮和弹窗。图标搭配使用FontAwesome图标库让操作更直观。一个典型的表单页面form th:action{/pet/save} th:object${pet} methodpost div classmb-3 label forpetName classform-label宠物名字/label input typetext classform-control idpetName th:field*{name} required /div div classmb-3 label forpetType classform-label类型/label select classform-select idpetType th:field*{type} option value请选择/option option value狗狗/option option value猫猫/option !-- ... -- /select /div button typesubmit classbtn btn-primary提交/button /form4.3 通过Ajax实现局部交互虽然主要使用页面跳转但在一些需要动态交互的地方比如删除前的确认、下拉框联动选择客户后动态加载其宠物使用jQuery的Ajax非常方便。// 删除客户前的确认与Ajax提交 function deleteClient(id) { if (confirm(确定要删除这个客户吗其关联的宠物信息也将无法查看)) { $.ajax({ url: /client/delete/ id, type: DELETE, // 使用RESTful风格的DELETE方法 success: function(result) { if (result.code 200) { alert(删除成功); location.reload(); // 刷新当前页面 } else { alert(删除失败 result.msg); } }, error: function() { alert(网络请求失败); } }); } }后端Controller需要处理DELETE请求并返回统一JSON格式RestController // 注意这里是RestController返回JSON RequestMapping(/client) public class ClientController { DeleteMapping(/delete/{id}) public Result deleteClient(PathVariable Long id) { boolean success clientService.removeById(id); if (success) { return Result.success(); } else { return Result.error(删除失败); } } } // 统一的返回结果封装类 Data public class Result { private Integer code; private String msg; private Object data; public static Result success() { ... } public static Result error(String msg) { ... } }5. 系统进阶功能与优化思考5.1 实现简单的权限控制对于一个管理系统不同角色的员工应有不同权限。例如店员只能查看和创建订单店长可以管理商品和服务项目。这里可以实现一个基于URL拦截的简单权限控制。定义权限注解创建一个RequiresRoles注解。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequiresRoles { String[] value(); // 允许的角色如 {admin, manager} }实现拦截器创建一个Spring MVC拦截器在请求到达Controller方法前进行拦截。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 判断是否为HandlerMethodController方法 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod (HandlerMethod) handler; // 2. 从Session中获取当前登录用户信息假设已登录 User loginUser (User) request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(/login); return false; } // 3. 获取方法上的RequiresRoles注解 RequiresRoles annotation handlerMethod.getMethodAnnotation(RequiresRoles.class); if (annotation ! null) { String[] requiredRoles annotation.value(); String userRole loginUser.getRole(); boolean hasRole Arrays.asList(requiredRoles).contains(userRole); if (!hasRole) { response.sendError(403, 权限不足); return false; } } return true; } }注册拦截器并配置拦截规则在Web配置类中排除对登录、静态资源等路径的拦截。在Controller方法上使用注解GetMapping(/service/manage) RequiresRoles({admin, manager}) // 只有管理员和店长能访问 public String serviceManage() { return service/manage; }5.2 数据统计与报表生成统计功能是管理系统的价值所在。可以利用MyBatis-Plus的查询结合MySQL的聚合函数轻松实现一些基础统计。示例统计近30天每日营收// 在OrderMapper.xml中编写SQL select idselectDailyRevenue resultTypejava.util.Map SELECT DATE(create_time) as date, SUM(total_amount) as revenue FROM order WHERE status 已完成 -- 只统计已完成的订单 AND create_time DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY DATE(create_time) ORDER BY date DESC /select在Service中调用这个Mapper方法将结果一个包含date和revenue的Map列表传递给前端。前端可以使用ECharts或Chart.js等开源图表库来绘制直观的折线图或柱状图。5.3 项目部署与运维考量开发完成后如何让系统跑起来Spring Boot让部署变得极其简单。打包在项目根目录执行mvn clean packageMaven项目会在target目录下生成一个可执行的jar文件如pet-management-0.0.1-SNAPSHOT.jar。运行将jar包和application.yml或application.properties配置文件放到服务器上使用命令java -jar pet-management-0.0.1-SNAPSHOT.jar即可启动。配置文件里需要正确配置数据库连接等信息。后台运行在生产环境通常使用nohup命令或配置为系统服务如systemd来保证应用在后台稳定运行。nohup java -jar pet-management-0.0.1-SNAPSHOT.jar app.log 21 访问应用默认在8080端口启动在服务器安全组开放该端口后即可通过http://服务器IP:8080访问。运维提示日志务必配置好日志如Logback将日志输出到文件并设置合理的滚动策略按天或按大小分割便于问题排查。数据库备份定期备份MySQL数据库是生命线。可以写一个Shell脚本用mysqldump命令备份并通过crontab定时执行。监控简单的监控可以用Spring Boot Actuator暴露一些健康检查端点复杂的可以集成Prometheus和Grafana。6. 开发中常见问题与排查实录在实际编码和调试过程中肯定会遇到各种问题。这里记录几个我印象比较深的“坑”及其解决方法。问题一MyBatis-Plus插入数据后实体对象ID为null现象调用mapper.insert(entity)后数据库里数据生成了自增ID也有但Java对象里的id属性还是null。原因MP默认不会将生成的主键值回填到实体对象中。解决在实体类的主键字段上添加TableId注解并指定type IdType.AUTO。Data TableName(pet) public class Pet { TableId(type IdType.AUTO) // 关键在这里 private Long id; // ... 其他字段 }问题二Thymeleaf页面获取不到Model中的数据现象Controller里明明model.addAttribute(data, someData)但页面上用${data}取不到值。排查检查Controller方法返回值是否正确指向了目标HTML模板路径。检查HTML文件是否放在src/main/resources/templates目录下。检查HTML文件头是否引入了Thymeleaf命名空间html xmlns:thhttp://www.thymeleaf.org。在Controller里打印一下someData确认数据不为空。问题三Ajax请求成功但进入error回调函数现象前端Ajax调用后端接口后端逻辑执行成功但前端触发了error回调。原因这通常是HTTP状态码非200导致的。可能的原因有后端Controller抛出了异常但没有被全局异常处理器捕获导致返回了500错误。后端方法返回的类型或格式不是前端Ajax期望的JSON。解决在后端实现一个全局异常处理类ControllerAdviceExceptionHandler将所有异常捕获并封装成统一的ResultJSON对象返回。确保Controller方法上使用了RestController或ResponseBody注解并且返回的是对象Spring会将其转为JSON。问题四数据库中文乱码现象页面提交的中文数据存入MySQL后变成问号???。解决这是一个经典问题。需要三处编码统一为UTF-8。数据库连接字符串在application.yml中配置JDBC URL时加上参数jdbc:mysql://localhost:3306/pet_db?useUnicodetruecharacterEncodingutf-8useSSLfalse。MySQL数据库/表创建创建数据库和表时指定字符集为utf8mb4推荐支持emoji。服务器连接器确保你的MySQL驱动版本较新支持UTF-8。这个宠物管理系统的实现过程实际上是一个微缩版的企业级应用开发演练。它强迫你去思考实体关系、事务边界、前后端协作和部署运维。虽然功能不复杂但把每个环节都做扎实收获远比单纯实现CRUD要大得多。如果你正在学习Java Web不妨按照这个思路亲手实现一遍过程中遇到的每一个问题都是你技术成长的垫脚石。本文还有配套的精品资源点击获取