MybatisPlus使用PageHelper做查询selectList前,进行selectList_COUNT功能的反编译代码逻辑——学习心得

最近项目出了一个BUG,具体情况如下:
项目使用SSM,M为加强版的MybatisPlus;
持久层Manager使用公用的实现,也就是说, 图1中,userManager和airManager这2个持久层所使用的方法getOneByWrapper走的是同样的代码流程PageHelper.startPage和dao.selectList;
图1
图2
产生的问题是:userManager的selectList_COUNT转化出来的SQL执行语句,和airManager的selectList_COUNT所转化出来的SQL执行语句,完全不一样,如图3及图4:
图3
图4

报错执行的SQL语句selectList_COUNT是:
airManager:
select count(0) from (SELECT id,air_number AS airNumber,enable,airworthiness,if_use AS ifUse,del_flag AS delFlag,create_time AS createTime,update_time AS updateTime,type,air_type AS airType,air_type_long AS airTypeLong,terminal,carrier,mainhold_count AS mainholdCount,bellyhold_count AS bellyholdCount,mainhold_name AS mainholdName,bellyhold_name_fwd AS bellyholdNameFwd,bellyhold_name_aft AS bellyholdNameAft FROM air WHERE del_flag=‘NORMAL’ AND (id = ?) ORDER BY create_time DESC) tmp_count
而userManager执行的SQL语句selectList_COUNT却是:
SELECT count(0) FROM sys_user WHERE del_flag = ‘NORMAL’ AND (id = ?)
相同的公用逻辑产生了不同的SQL执行语句,我对问题进行了一番定位,终于找到问题。

在这里插入图片描述
在这里插入图片描述
刚开始的时候,还以为是实体持久层的入库SQL描述描述文件xml出了问题,以为是描述少了一个air_number表字段而导致的,后来即使补充完整,对准顺序,问题依旧存在不变。
对反编译文件class看代码来定位问题。
整个Exception错误栈日志过程是:

at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:89)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
	at com.sun.proxy.$Proxy2
<think>我们之已经给出了家庭菜谱管理系统的完整代码,现在需要详细解释每个文件的功能。下面是对每个文件的详细说明: ### 1. `data_structures.h` **功能**:定义系统中使用的数据结构和全局变量。 - 定义了常量(如最大长度、最大数量等)。 - 定义了枚举类型:`MealType`(餐型)、`Taste`(口味)、`State`(状态)、`Cuisine`(菜系)。 - 定义了菜谱结构体`Recipe`,包含菜谱的所有信息和一个指向下一个菜谱的指针(用于链表)。 - 声明了全局变量:`recipe_list`(菜谱链表头指针)、`recipe_count`(菜谱数量)、`lovers_list`(喜好者列表)、`lover_count`(喜好者数量)。 ### 2. `recipe_manager.h` **功能**:声明菜谱管理相关的函数。 - 声明了核心功能函数:`add_recipe`(添加)、`delete_recipe`(删除)、`modify_recipe`(修改)、`search_recipes`(查找)、`random_recommendation`(随机推荐)。 - 声明了辅助函数:`print_recipe`(打印菜谱详情)、`find_recipe_by_id`(按ID查找)、`find_recipe_by_name`(按名称查找)、`get_multi_select_options`(获取多选选项)、`get_meal_type`(获取餐型)、`get_cuisine`(获取菜系)、`get_lovers`(获取喜好者)。 ### 3. `file_io.h` **功能**:声明文件操作相关的函数。 - `save_data`:将菜谱链表保存到二进制文件。 - `load_data`:从二进制文件加载菜谱链表。 - `save_lovers`:将喜好者列表保存到文本文件。 - `load_lovers`:从文本文件加载喜好者列表。 ### 4. `main.c` **功能**:程序入口,主循环和主菜单。 - 包含`main`函数:程序入口点。 - 全局变量的定义(在头文件中声明,在此处定义)。 - `display_main_menu`:显示主菜单。 - `lover_management`:处理喜好者管理(添加、删除、查看)。 - 主循环:根据用户选择调用相应功能或退出。 ### 5. `recipe_manager.c` **功能**:实现菜谱管理的核心功能。 - `get_multi_select_options`:实现多选选项的输入(使用位掩码)。 - `get_meal_type`:获取餐型(通过菜单选择)。 - `get_cuisine`:获取菜系(通过菜单选择)。 - `get_lovers`:从喜好者列表中选择喜好者。 - `print_recipe`:打印菜谱的详细信息(将枚举和位掩码转换为可读字符串)。 - `find_recipe_by_id`和`find_recipe_by_name`:分别在链表中按ID或名称查找菜谱。 - `add_recipe`:添加新菜谱,引导用户输入菜谱的各个字段,并加入链表。 - `delete_recipe`:按ID或名称查找菜谱,确认后删除。 - `modify_recipe`:按ID或名称查找菜谱,选择要修改的字段进行修改。 - `search_recipes`:提供多种查找方式(ID、名称、餐型、口味、状态、菜系、显示全部)。 - `random_recommendation`:随机推荐菜谱,支持一餐或多天推荐(多天推荐时生成每天的餐食安排)。 ### 6. `file_io.c` **功能**:实现数据的保存和加载。 - `save_data`:将菜谱链表以二进制形式写入文件(注意:直接写入结构体,但链表指针不保存,保存和加载时链表关系重建)。 - `load_data`:从二进制文件读取菜谱数据,重建链表(注意:读取时每个菜谱动态分配内存,并连接成链表)。 - `save_lovers`:将喜好者列表(字符串数组)按行写入文本文件。 - `load_lovers`:从文本文件读取喜好者列表。 ### 7. `Makefile` **功能**:构建系统的编译脚本。 - 定义编译器(g++,虽然代码是C,但这里使用了g++,也可以改为gcc)和编译选项。 - 定义目标程序名`recipe_system`。 - 定义源文件和头文件。 - 提供`all`(默认目标)、`clean`(清理)、`run`(编译并运行)等规则。 ### 补充说明 - **链表操作**:菜谱使用单向链表管理,添加时采用头插法。 - **位掩码**:口味和状态是多选的,使用整数的不同位来表示,例如,`SOUR`对应第0位(1),`SWEET`对应第1位(2),以此类推。多选时使用按位或操作组合,检查时使用按位与操作。 - **文件存储**:菜谱数据用二进制格式保存,但注意链表中的指针并不保存,保存的是每个节点的数据,加载时重新构建链表。喜好者用文本格式保存。 - **随机推荐**:一餐推荐时,先收集符合条件的菜谱,然后随机选择。多天推荐时,按天和餐型分别收集候选菜谱,然后随机选择,避免重复。 ### 注意事项 - 该代码在保存链表时,每个节点被依次写入文件。但是,当结构体中有指针时(如链表节点的`next`指针),直接保存指针的值是无效的(因为下次运行地址会变)。因此,我们只保存数据,在加载时重新构建链表。 - 多天推荐中的食材采购清单功能尚未实现,作为扩展点。 ### 编译运行 - 在终端进入项目目录,执行`make`进行编译,然后执行`make run`运行程序。 - 程序运行后,数据文件(`recipes.dat`和`lovers.dat`)将保存在当目录。 §§相关问题§§: 1. 在C语言中,链表与数组相比有哪些优缺点? 2. 如何改进文件存储方式以支持更复杂的数据结构(如多级链表)? 3. 随机推荐功能中,如何避免重复推荐同一道菜? 4. 在多天推荐中,如何实现食材采购清单的生成?</think>下面是对家庭菜谱管理系统每个文件功能的详细解释,包括其核心职责和关键实现细节: ### 1. `data_structures.h` - 数据结构定义 **功能**:定义系统使用的核心数据结构和全局变量 ```c #ifndef DATA_STRUCTURES_H #define DATA_STRUCTURES_H #include <stdbool.h> // 系统常量定义(最大长度限制) #define MAX_NAME_LEN 50 // 名称最大长度 #define MAX_INGREDIENTS 20 // 食材最大数量 #define MAX_STEPS 20 // 步骤最大数量 #define MAX_LOVERS 10 // 喜好者最大数量 #define MAX_RECIPES 100 // 菜谱最大数量 // 枚举定义(餐型、口味、状态、菜系) typedef enum { BREAKFAST, MAIN_COURSE, DESSERT, SNACK, DRINK } MealType; // 餐型枚举 typedef enum { SOUR = 1, SWEET = 2, BITTER = 4, SPICY = 8, SALTY = 16, COLD = 32, HOT = 64 } Taste; // 口味枚举(位掩码设计) // ...(其他枚举定义) // 菜谱核心结构体 typedef struct Recipe { int id; // 唯一标识符 char name[MAX_NAME_LEN]; MealType meal_type; int taste_flags; // 位掩码存储多选口味 // ...(其他属性) struct Recipe* next; // 链表指针 } Recipe; // 全局数据声明 extern Recipe* recipe_list; // 菜谱链表头指针 extern int recipe_count; // 当菜谱数量 extern char lovers_list[MAX_LOVERS][MAX_NAME_LEN]; // 喜好者列表 extern int lover_count; // 当喜好者数量 #endif ``` **关键点**: - 使用位掩码技术处理多选属性(口味、状态) - 链表结构管理菜谱数据 - 全局变量声明供其他文件使用 ### 2. `recipe_manager.h` - 功能接口声明 **功能**:声明所有菜谱管理功能的接口 ```c #ifndef RECIPE_MANAGER_H #define RECIPE_MANAGER_H #include "data_structures.h" // 核心功能声明 void add_recipe(); // 添加菜谱 void delete_recipe(); // 删除菜谱 void modify_recipe(); // 修改菜谱 void search_recipes(); // 查找菜谱 void random_recommendation();// 随机推荐 // 辅助函数声明 void print_recipe(Recipe* recipe); // 打印菜谱详情 Recipe* find_recipe_by_id(int id); // 按ID查找 // ...(其他辅助函数) #endif ``` **关键点**: - 声明CRUD(增删改查)核心功能 - 定义辅助函数接口 - 提供数据访问抽象层 ### 3. `file_io.h` - 文件操作接口 **功能**:声明数据持久化相关函数 ```c #ifndef FILE_IO_H #define FILE_IO_H #include "data_structures.h" // 文件操作接口 void save_data(); // 保存菜谱数据 void load_data(); // 加载菜谱数据 void save_lovers(); // 保存喜好者数据 void load_lovers(); // 加载喜好者数据 #endif ``` **关键点**: - 数据持久化接口 - 分离数据存储逻辑 - 支持二进制和文本格式存储 ### 4. `main.c` - 程序入口和主控 **功能**:系统入口、主循环和菜单控制 ```c #include "data_structures.h" #include "recipe_manager.h" #include "file_io.h" // 全局变量定义(实际存储) Recipe* recipe_list = NULL; int recipe_count = 0; // ... // 主菜单显示 void display_main_menu() { printf("\n家庭菜谱管理系统\n"); printf("1. 添加菜谱\n"); // ...(其他菜单项) } // 喜好者管理子菜单 void lover_management() { printf("\n喜好者管理\n"); printf("1. 添加喜好者\n"); // ... } // 主函数 int main() { load_data(); // 启动时加载数据 load_lovers(); while (running) { display_main_menu(); // 处理用户选择 switch(choice) { case 1: add_recipe(); break; // ...(其他功能调用) } } save_data(); // 退出时保存数据 return 0; } ``` **关键点**: - 程序入口和主循环 - 菜单导航控制 - 数据初始化和清理 - 功能调度中心 ### 5. `recipe_manager.c` - 核心业务逻辑 **功能**:实现所有菜谱管理功能 ```c #include "data_structures.h" #include "recipe_manager.h" // 多选选项处理(位掩码应用) void get_multi_select_options(const char* prompt, int* flags, const char* options[], int count) { printf("%s (输入空行结束):\n", prompt); for (int i = 0; i < count; i++) { printf("%d. %s\n", i+1, options[i]); } *flags = 0; // 初始化位掩码 while (1) { fgets(input, sizeof(input), stdin); if (input[0] == '\n') break; int choice = atoi(input); *flags |= (1 << (choice-1)); // 设置对应位 } } // 添加菜谱实现 void add_recipe() { Recipe* new_recipe = malloc(sizeof(Recipe)); new_recipe->id = recipe_count + 1; // 自动生成ID // 获取用户输入 printf("输入菜名: "); fgets(new_recipe->name, MAX_NAME_LEN, stdin); // ...(其他字段输入) // 链表操作(头插法) new_recipe->next = recipe_list; recipe_list = new_recipe; recipe_count++; } // 随机推荐算法 void random_recommendation() { srand(time(NULL)); // 初始化随机种子 if (choice == 1) { // 单餐推荐 // 收集符合条件的候选菜谱 Recipe* candidates[MAX_RECIPES]; int candidate_count = 0; Recipe* current = recipe_list; while (current) { if (current->meal_type == meal_type) { candidates[candidate_count++] = current; } current = current->next; } // 随机选择 for (int i = 0; i < count; i++) { int idx = rand() % candidate_count; print_recipe(candidates[idx]); // 避免重复:替换为最后一个元素 candidates[idx] = candidates[--candidate_count]; } } // ...(多天推荐实现) } ``` **关键点**: - 位掩码技术的实际应用 - 链表操作(添加、删除、遍历) - 随机算法实现(Fisher-Yates洗牌变体) - 用户输入处理和多选交互 - 复杂查询逻辑实现 ### 6. `file_io.c` - 数据持久化实现 **功能**:实现数据存储和加载的具体逻辑 ```c #include "data_structures.h" // 保存菜谱数据(链表到二进制文件) void save_data() { FILE* file = fopen("recipes.dat", "wb"); Recipe* current = recipe_list; while (current) { // 注意:不保存next指针(只保存数据) fwrite(current, sizeof(Recipe) - sizeof(Recipe*), 1, file); current = current->next; } fclose(file); } // 加载菜谱数据(二进制文件到链表) void load_data() { FILE* file = fopen("recipes.dat", "rb"); Recipe* last = NULL; while (!feof(file)) { Recipe* new_recipe = malloc(sizeof(Recipe)); if (fread(new_recipe, sizeof(Recipe) - sizeof(Recipe*), 1, file) != 1) { free(new_recipe); break; } new_recipe->next = NULL; // 重建链表 if (!recipe_list) recipe_list = new_recipe; else last->next = new_recipe; last = new_recipe; recipe_count++; } fclose(file); } // 文本格式保存喜好者 void save_lovers() { FILE* file = fopen("lovers.dat", "w"); for (int i = 0; i < lover_count; i++) { fprintf(file, "%s\n", lovers_list[i]); } fclose(file); } ``` **关键点**: - 链表数据的序列化和反序列化 - 二进制和文本格式混合存储 - 指针处理(不保存指针值) - 错误处理和内存管理 ### 7. `Makefile` - 构建系统 **功能**:自动化编译和项目管理 ```makefile CC = gcc # C编译器 CFLAGS = -Wall -Wextra -std=c99 -g # 编译选项 TARGET = recipe_system # 目标程序名 SOURCES = main.c recipe_manager.c file_io.c # 源文件 HEADERS = data_structures.h recipe_manager.h file_io.h # 头文件 # 默认构建目标 all: $(TARGET) # 链接规则 $(TARGET): $(SOURCES) $(HEADERS) $(CC) $(CFLAGS) -o $(TARGET) $(SOURCES) # 清理构建产物 clean: rm -f $(TARGET) *.dat # 运行程序 run: $(TARGET) ./$(TARGET) .PHONY: all clean run ``` **关键点**: - 自动化编译流程 - 依赖关系管理 - 常用命令封装(clean, run) - 跨平台支持(Linux/macOS) ### 系统架构总结 1. **分层架构**: - 数据层(data_structures) - 业务层(recipe_manager) - 持久层(file_io) - 表示层(main) 2. **模块化设计**: - 每个文件职责单一 - 通过头文件定义接口 - 低耦合高内聚 3. **关键技术**: - 链表数据结构 - 位掩码多选处理 - 文件序列化 - 随机算法 - 用户交互设计 4. **扩展性**: - 通过链表支持动态增长 - 模块化设计易于功能扩展 - 数据格式支持新属性添加
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值