大家好,我是松柏。时隔多日,我再次被 Lombok 坑了一手,还是跟 MyBatis 联手干的。今天通过我的心路历程来给大家分享下我解决这个 bug 的思路。
情景复现
事情是这样的,那天我正在快乐的增删改查,写下了这样一段代码:
@Resource
BookService bookService;
@Test
void contextLoads() {
bookService.lambdaQuery()
.select(Book::getId, Book::getName)
.list();
}
这能有啥问题?哥们直接执行,然后:
Caused by: java.lang.NumberFormatException: For input string: "eeee"
我:???
看了一眼 Book 实体类:
@TableName(value ="book")
@Builder
@Data
public class Book implements Serializable {
/**
*
*/
@TableId
private Long id;
/**
* 价格
*/
private Integer price;
/**
* 书名
*/
private String name;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
没毛病吧?没毛病吗?应该吧,都快给我整不自信了。
难道是玄学?我得重新执行试下,还是报错。
难道是缓存?clean 一下重新执行,还是报错。
好吧,看来是真摊上事了。
解决问题
突然,我看到了实体类上的 @Builder 注解:
让我想起了 @Builder 会生成一个全参构造并且导致无参构造丢失,难道是这个问题?
加个无参构造 @NoArgsConstructor 试试:
好吧,@Builder 需要一个适当的构造器,再加一个全参构造好了:
@TableName(value ="book")
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
...
}
执行一下,成功了!!!
这里问题的解决凭的是直觉,不太可取,接下来我们尝试分析下原因。
定位原因
找到异常抛出前的位置,打个断点:
ok,运行,看看方法栈里有没有可疑方法。测试类前面不用管,都没执行到我们的代码,问题肯定不在那,直接往后看。这时候大概看一眼方法名,这里有个 getInt 肯定是不对劲的,因为我这里是 String 类型,所以问题基本就锁定在测试类到 getInt 这个方法之间了:
那我们从 getInt 往前看,看看是哪里的问题让它走到了这个方法。然后就能找到 applyColumnOrderBasedConstructorAutomapping
这个方法,因为它里面的内容太显眼了,给我 String 类型的 name 字段匹配成了 Integer 类型,这不搞笑吗:
而且我们看这个方法名:基于列顺序构造器的自动映射(原谅我的蹩脚英语),有点拗口,反正大致意思就是说:诶,我这里是根据构造器的参数顺序进行自动映射。而且前面我们提到,@Builder 会生成一个全参构造,其实目前我们的 Book 类也只有一个构造器:
Book(Long id, Integer price, String name) {
this.id = id;
this.price = price;
this.name = name;
}
还记得我的查询语句吗:
bookService.lambdaQuery()
.select(Book::getId, Book::getName)
.list();
我指定了只查 id 和 name 字段,但是使用的是全参构造的参数顺序进行映射,那就把 price 的 Integer 类型映射到 name 上了,这就是这个问题的根源所在。
那为什么我们加了无参构造就好用了呢?再往前看一点,找到createResultObject
方法就明白了:
有无参构造的时候,通过工厂直接创建了对象;没有无参构造则会尝试自动映射,但是在我们这种情况下又会映射失败,导致了异常。
最后补一句,如果name字段的值不包含字母 e ,则会抛出另一个异常:
Caused by: com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from string 'aaaa'
原因在这里:
结语
收工,这样就搞明白这个问题的始末了。又转念一想,难道我是第一个发现这个问题的人?搜一下看看:
好吧,白忙活了,早有前人栽树了,寻根问底之前应该先搜一下的,不过自己找问题的过程还是挺有成就感的,哈哈。
本篇就到这里了,有帮助的话点个关注和赞吧,拜拜👋🏻!