# 患者提醒数据管理功能修复经历 ## 问题描述 在患者提醒数据管理功能中,API响应中的Boolean字段(如`notificationEnabled`、`bloodPressureEnabled`等)始终返回`null`,尽管数据库中存储的是正确的TINYINT(1)值(1表示true,0表示false)。 ### 数据库记录示例 ``` id: 1991527305027313666 patient_user_id: 1988147181088956418 is_notification_enabled: 1 is_subscription_available: 1 is_blood_pressure_enabled: 0 is_blood_sugar_enabled: 0 is_heart_rate_enabled: 0 is_medication_enabled: 0 ``` ### API响应示例 ```json { "code": 200, "message": "ok", "data": { "reminder": { "id": "1991527305027313666", "patientUserId": "1988147181088956418", "notificationEnabled": null, "subscriptionAvailable": null, "bloodPressureEnabled": null, "bloodSugarEnabled": null, "heartRateEnabled": null, "medicationEnabled": null, "bloodPressureTimes": [], "bloodSugarTimes": [], "heartRateTimes": [], "medicationEnabled": null, "createTime": "2025-11-20T23:21:25" } } } ``` ## 错误思路 ### 1. 最初怀疑BeanUtils.copyProperties问题 - **思路**:认为BeanUtils无法正确复制Boolean字段 - **尝试**:手动设置Boolean字段赋值 - **结果**:问题仍然存在,说明根本原因不在BeanUtils ### 2. 怀疑MyBatis映射配置问题 - **思路**:检查application.yml中的MyBatis配置 - **发现**:配置正常,map-underscore-to-camel-case=true - **结果**:配置无问题 ### 3. 尝试修改SQL查询强制转换 - **思路**:使用`CAST(field AS SIGNED)`或`field + 0`强制转换为数字 - **尝试**:修改@Select注解中的SQL查询 - **结果**:查询SQL改变了,但实际执行的仍然是`SELECT *`,说明可能有缓存或配置问题 ### 4. 怀疑字段名映射问题 - **思路**:检查PO中的@TableField注解 - **发现**:注解正确,字段名匹配 - **结果**:映射配置无问题 ## 关键性解决思路 ### 核心问题识别 通过调试日志发现,MyBatis查询确实返回了正确的数据: ``` ==> Preparing: SELECT * FROM t_patient_reminder WHERE patient_user_id = ? ==> Parameters: 1988147181088956418(Long) <== Columns: ..., 1, 1, 0, <>, 0, <>, 0, <>, 0, ... ``` 但PO对象中的Boolean字段仍然为null。这说明MyBatis无法将TINYINT(1)正确映射到Java的Boolean类型。 ### 根本原因 MySQL Connector/J 5.1.49版本对TINYINT(1)的处理方式与预期不符。在某些情况下,TINYINT(1)被映射为其他类型而不是Boolean。 ### 解决方案 1. **改变PO字段类型**:将Boolean改为Byte类型 2. **手动类型转换**:在Service层进行Byte到Boolean的转换 3. **简化Mapper查询**:使用MyBatis Plus的标准查询方式 ## 修复过程 ### 第一阶段:识别问题 1. 添加调试日志,确认数据库查询正常但PO字段为null 2. 确认BeanUtils.copyProperties不是问题根源 3. 确定是MyBatis映射问题 ### 第二阶段:尝试SQL转换 1. 修改Mapper的@Select查询,使用CAST和+0操作 2. 编译测试,发现实际执行的SQL未改变 3. 怀疑有缓存或热重载问题 ### 第三阶段:改变字段类型 1. 将PO中的Boolean字段改为Integer 2. 修改Service中的转换逻辑 3. 测试发现仍然无效 ### 第四阶段:使用Byte类型 1. 将PO中的Integer字段改为Byte 2. Byte类型更适合TINYINT(1)的映射 3. 修改所有转换逻辑 ### 第五阶段:简化Mapper 1. 移除自定义@Select注解 2. 使用QueryWrapper进行查询 3. 确保MyBatis Plus的自动映射生效 ## 最终修复代码 ### PO实体类修改 ```java // 原来 private Boolean notificationEnabled; // 修改后 private Byte notificationEnabled; ``` ### Service转换逻辑 ```java // 查询时转换 reminderResponse.setNotificationEnabled( patientReminder.getNotificationEnabled() != null && patientReminder.getNotificationEnabled() == 1 ); // 保存时转换 patientReminder.setNotificationEnabled( request.getNotificationEnabled() ? (byte)1 : (byte)0 ); ``` ### Mapper简化 ```java // 原来 @Select("SELECT ... FROM t_patient_reminder WHERE patient_user_id = #{patientUserId}") PatientReminder selectByPatientUserId(@Param("patientUserId") Long patientUserId); // 修改后 PatientReminder selectByPatientUserId(@Param("patientUserId") Long patientUserId); // 在Service中使用QueryWrapper查询 ``` ## 经验总结 ### 1. 调试技巧 - **添加调试日志**:在关键位置打印对象字段值 - **检查SQL执行**:确认实际执行的SQL和参数 - **验证数据流**:从数据库到PO到VO的完整数据流 ### 2. MyBatis映射经验 - **TINYINT(1)映射问题**:在某些JDBC驱动版本中可能无法正确映射到Boolean - **类型选择**:Byte类型比Integer更适合TINYINT映射 - **避免自定义SQL**:优先使用MyBatis Plus的自动映射 ### 3. 问题排查思路 - **从外到内**:先检查配置、注解,再检查类型映射 - **分层验证**:数据库→Mapper→Service→Controller逐层验证 - **最小化修改**:每次只修改一个变量,便于定位问题 ### 4. 架构设计建议 - **类型一致性**:数据库、PO、VO的类型应保持一致或有明确转换 - **异常处理**:对可能为null的值进行防御性编程 - **文档同步**:代码修改后及时更新相关文档 ### 5. 工具使用经验 - **QueryWrapper**:比自定义SQL更稳定可靠 - **BeanUtils**:适用于简单对象复制,复杂转换需手动处理 - **@TableField**:确保字段映射正确 ## 修复成果 修复后API响应正确: ```json { "notificationEnabled": true, "subscriptionAvailable": true, "bloodPressureEnabled": false, "bloodSugarEnabled": false, "heartRateEnabled": false, "medicationEnabled": false } ```