患者提醒数据管理功能修复经历.md 5.7 KB

患者提醒数据管理功能修复经历

问题描述

在患者提醒数据管理功能中,API响应中的Boolean字段(如notificationEnabledbloodPressureEnabled等)始终返回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响应示例

{
  "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, <<BLOB>>, 0, <<BLOB>>, 0, <<BLOB>>, 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实体类修改

// 原来
private Boolean notificationEnabled;

// 修改后
private Byte notificationEnabled;

Service转换逻辑

// 查询时转换
reminderResponse.setNotificationEnabled(
    patientReminder.getNotificationEnabled() != null &&
    patientReminder.getNotificationEnabled() == 1
);

// 保存时转换
patientReminder.setNotificationEnabled(
    request.getNotificationEnabled() ? (byte)1 : (byte)0
);

Mapper简化

// 原来
@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响应正确:

{
  "notificationEnabled": true,
  "subscriptionAvailable": true,
  "bloodPressureEnabled": false,
  "bloodSugarEnabled": false,
  "heartRateEnabled": false,
  "medicationEnabled": false
}