MyBatis 隐式类型转换问题与解决方案

📋 问题概述

MyBatis 在使用 OGNL 表达式和自动类型映射时存在多种隐式类型转换问题,主要涉及动态 SQL 判断、数据库与 Java 类型映射等方面。

🐞 常见问题及解决方案

1. 字符串与字符判断混淆

问题描述

  • OGNL 表达式将单引号括起的单个字符(如 'Y')解析为 char 类型
  • 双引号字符串单引号多字符(如 "Y"'日')解析为 String 类型
  • 两者比较时可能抛出 NumberFormatException

解决方案

<!-- 方法1:使用 .toString() -->
<if test="status == 'Y'.toString()">
    AND status = 'Y'
</if>

<!-- 方法2:颠倒引号使用(外层单引号,内层双引号) -->
<choose>
    <when test='params.selectParam == "1"'>
        GROUP BY t1.category
    </when>
</choose>

2. 数值零值判断失效

问题描述

  • OGNL 将数字 0 视同 false
  • 当字段值为 0 时,<if test="flag"> 条件不成立

解决方案

<!-- 错误方式 -->
<if test="flag"> <!-- 当flag=0时条件不成立 -->
    AND flag = #{flag}
</if>

<!-- 正确方式 -->
<if test="flag != null"> <!-- 明确判断非空 -->
    AND flag = #{flag}
</if>

3. 数据库类型与Java类型映射不当

常见问题场景

  • BIGINT UNSIGNED 使用 Long 接收可能溢出
  • 精度要求高的数值类型使用不匹配的Java类型
  • 数据库存储非数字字符但Java字段定义为数值类型

推荐映射关系

数据库类型 推荐Java类型 不推荐Java类型
BIGINT UNSIGNED BigInteger Long
DECIMAL(高精度) BigDecimal Double/Float
TINYINT(状态位) Integer Boolean
VARCHAR(存储数字) String Integer/Long

解决方案

// 实体类正确定义
public class User {
    private BigInteger bigId; // 对应 BIGINT UNSIGNED
    private BigDecimal balance; // 对应 DECIMAL
    private String statusCode; // 对应存储'Y','N'的VARCHAR
}

4. 日期时间时区问题

问题描述

  • TIMESTAMP 类型会涉及时区转换
  • DATETIME 类型不涉及时区转换
  • 两者混用可能导致时间显示不一致

解决方案

-- 根据业务需求选择合适类型
CREATE TABLE example (
    create_time TIMESTAMP,   -- 需要时区统一
    event_time DATETIME      -- 严格存储字面值
);
// 明确时区处理
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    
    // 设置时区
    Properties props = new Properties();
    props.setProperty("serverTimezone", "Asia/Shanghai");
    factory.setConfigurationProperties(props);
    
    return factory.getObject();
}

5. 枚举类型转换异常

问题描述

  • 默认按枚举名称(name())匹配
  • 数据库存储数字代码时转换失败
  • 未定义的枚举值导致转换异常

解决方案

// 自定义枚举TypeHandler
@MappedTypes(StatusEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class StatusEnumTypeHandler extends BaseTypeHandler<StatusEnum> {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
                                  StatusEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode()); // 存储枚举对应的数字代码
    }

    @Override
    public StatusEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return StatusEnum.fromCode(code); // 从数字代码转换回枚举
    }
    
    // 其他重写方法...
}
<!-- 注册TypeHandler -->
<typeHandlers>
    <typeHandler handler="com.example.StatusEnumTypeHandler"/>
</typeHandlers>

<!-- 或在resultMap中指定 -->
<resultMap id="userMap" type="User">
    <result column="status" property="status" 
            typeHandler="com.example.StatusEnumTypeHandler"/>
</resultMap>

🛠️ 最佳实践

1. 预防措施

<!-- 明确指定jdbcType -->
<insert id="insertUser">
    INSERT INTO user (name, age, status)
    VALUES (
        #{name, jdbcType=VARCHAR},
        #{age, jdbcType=INTEGER},
        #{status, jdbcType=CHAR}
    )
</insert>

2. 日志监控

# 开启MyBatis SQL日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.mapper=DEBUG

3. 数据验证

// 在Service层进行数据验证
@Service
public class UserService {
    
    @Transactional
    public void createUser(User user) {
        // 数据验证
        if (user.getAge() != null && user.getAge() < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
        
        // 类型转换预处理
        if (user.getStatusCode() != null) {
            user.setStatusCode(user.getStatusCode().toUpperCase());
        }
        
        userMapper.insert(user);
    }
}

📊 问题排查流程

  1. 确认错误场景:动态SQL判断/结果集映射/参数设置
  2. 检查类型匹配:数据库字段类型 ↔ Java属性类型
  3. 验证数据内容:实际存储值是否符合预期格式
  4. 查看OGNL表达式:单引号/双引号使用是否正确
  5. 考虑自定义处理:是否需要编写TypeHandler

💡 总结建议

  1. 保持类型一致性:数据库设计时确保与Java类型合理匹配
  2. 显式优于隐式:明确指定jdbcType和使用.toString()方法
  3. 日志辅助调试:开启SQL日志便于发现问题
  4. 自定义处理复杂场景:善用TypeHandler处理特殊转换需求
  5. 数据验证前置:在业务层进行数据格式验证和清理
posted @ 2025-09-01 19:58  槑孒  阅读(73)  评论(0)    收藏  举报