Lombok 是一种 Java 实用工具,可帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO),它通过注解来实现这一目的。但是当 Lombok 和 Mybatis 相遇时,会产生一些意想不到的结果。
问题描述
今天发版,监控线上日志。发现存在如下报错:
日志截图
关键堆栈信息
1 | org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'rule_desc_code' from result set. Cause: java.sql.SQLException: Bad format for Timestamp '301' in column 13. |
产生疑问:rule_desc_code 明明是 String 类型,怎么会映射成 Timestamp 类型呢?它跟第 13 列又有什么关系?
原因定位
根据完整堆栈中的信息(未贴出),定位到报错的是 PayRouteRuleRecordMapper.xml 中的 selectByGatewayPayNos 方法。
仔细检查了一遍,发现 SQL 没有问题。
继续查看堆栈信息,找到了如下报错类。
于是按照方法一步一步点进去,终于找到了这里。发现 Mybatis 会将构造器中的入参与查询结果中的参数一一对应起来。
再来查看实体类、建表语句、构造方法。仔细查看建表语句和构造方法中的第 13 个字段。
实体类
1 | @Data |
建表语句
构造方法
一切疑惑都解开了。
- 因为 PayRouteRuleRecord 类中加了 @Builder 注解,Lombok 会自动生成全参构造方法,构造器中的参数顺序与实体类中定义的顺序保持一致。
- ruleDescCode 字段是后加的,实体类中字段的顺序与 sql 语句中的顺序不一致,从而导致 Mybatis 将 ruleDescCode 字段映射到了 lastUpdateTime 字段上。 也就有了开头的报错
Error attempting to get column 'rule_desc_code' from result set. Cause: java.sql.SQLException: Bad format for Timestamp '301' in column 13.
。
修复方案
方案一
既然知道了问题是因为字段顺序不一致导致的,那么调整一下实体类的字段顺序,使其与 sql 语句中的顺序一致即可。
- 优点
- 修复快。
- 缺点
- 没有从根本上修复该问题。
- 如果其他人调整了实体类的字段顺序,此问题还会出现。
还有更优的修复方案吗?
方案二
阅读 DefaultResultSetHandler.createResultObject() 方法时发现,如果实体类中存在无参构造方法,就会通过 objectFactory.create(resultType) 方法直接生成结果对象(利用反射实现),不用再一一比对实体类和返回结果中的字段。
所以只需要给实体类加上 @AllArgsConstructor 和 @NoArgsConstructor 注解,让实体类拥有无参构造方法即可。
修复前
修复后
小结
- @Data 注解会给实体类的所有属性生成 get/set 方法,以及实现 hashCode()、toString()、equals() 方法,并且生成一个无参数的构造方法。
- @Builder 注解除了会生成相应的 Build 模式的代码外,还会生成一个全参数的构造方法,参数的顺序就是属性定义的顺序。
- 但如果 @Data 注解和 @Builder 注解一块使用的话就只会生成全参构造方法,不会有无参构造方法。
- 鉴于许多开源框架都会用到实体类的无参构造方法,因此建议 @Data、@Builder、@AllArgsConstructor、@NoArgsConstructor 四个注解同时使用,避免踩坑。