java mybatis sql 查询耗时_Mybatis反序列化sql查询结果异常

bc92c2cf8bd8bc1de98ed75fcf120693.gif

点击上方蓝字关注我们

点击下方在在看在走

426bce4ec28b84a1775da211b068d15b.png

一、前言

今天调试分页查询代码的时候遇到一个奇葩的问题,该问题后来排查下来跟lombok的使用有关。我们在使用mybatis或者mybatis-plus的时候一般会定义一个类对应表的每个字段,一个成熟的java程序员喜欢使用lombok把代码简洁点。这是大前提,我直接说结论吧:

实体类最好都加上@Data,@AllArgsConstructor,@NoArgsConstructor才能避免我现在遇到的问题

二、我的问题

我的表:

 1create table unimall.industry_output_value
2(
3    id bigint auto_increment
4        primary key,
5    month_output_value decimal(20,10) null,
6    total_output_value decimal(20,10) null,
7    total_increase_percent decimal(20,10) null,
8    gmt_create datetime null,
9    gmt_update datetime null,
10    month datetime null
11);

我的实体类:

 1@Data
2@TableName("industry_output_value")
3@Builder
4public class OutputValueDO extends SuperDO {
5    private BigDecimal monthOutputValue;
6    private BigDecimal totalOutputValue;
7    private BigDecimal totalIncreasePercent;
8    @TableField("`month`")
9    private Date month;
10}
11@Data
12public class SuperDO {
13
14    private Long id;
15
16    @TableField("gmt_update")
17    private Date gmtUpdate;
18
19    @TableField("gmt_create")
20    private Date gmtCreate;
21
22}

然后一个普通的select查询的时候报了下面这个错误:

 1Caused by: org.springframework.dao.DataIntegrityViolationException: Error attempting to get column 'total_increase_percent' from result set.  Cause: java.sql.SQLDataException: Unsupported conversion from DECIMAL to java.sql.Timestamp
2; Unsupported conversion from DECIMAL to java.sql.Timestamp; nested exception is java.sql.SQLDataException: Unsupported conversion from DECIMAL to java.sql.Timestamp
3    at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:84) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
4    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
5    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
6    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:88) ~[mybatis-spring-2.0.3.jar:2.0.3]
7    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) ~[mybatis-spring-2.0.3.jar:2.0.3]
8    at com.sun.proxy.$Proxy84.selectList(Unknown Source) ~[na:na]
9  ...
10Caused by: java.sql.SQLDataException: Unsupported conversion from DECIMAL to java.sql.Timestamp
11    ...
12    at org.apache.ibatis.type.DateTypeHandler.getNullableResult(DateTypeHandler.java:39) ~[mybatis-3.5.3.jar:3.5.3]
13    at org.apache.ibatis.type.DateTypeHandler.getNullableResult(DateTypeHandler.java:28) ~[mybatis-3.5.3.jar:3.5.3]
14    at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:81) ~[mybatis-3.5.3.jar:3.5.3]
15  ...
16Caused by: com.mysql.cj.exceptions.DataConversionException: Unsupported conversion from DECIMAL to java.sql.Timestamp
17    at com.mysql.cj.result.DefaultValueFactory.unsupported(DefaultValueFactory.java:47) ~[mysql-connector-java-8.0.15.jar:8.0.15]
18    ...

可以看到异常很奇怪,我的total_increase_percent明明是BigDecimal,为何要被反序列化为java.util.Date呢?\
后面修改为如下代码就正常了:

 1@Data
2@TableName("industry_output_value")
3@Builder
4@AllArgsConstructor
5@NoArgsConstructor
6public class OutputValueDO extends SuperDO {
7    private BigDecimal monthOutputValue;
8    private BigDecimal totalOutputValue;
9    private BigDecimal totalIncreasePercent;
10    @TableField("`month`")
11    private Date month;
12}

二、问题排查

  1. 首先定位到问题出现在哪里

    57ed42f457d53a8d52325c472c61cfdc.png
    在这里插入图片描述

    由于 columnNametotal_increase_percent,该列不是timestamp,类型不一致,所以报异常。所以要看卡rs是如何获取到的以及为何使用了org.apache.ibatis.type.DateTypeHandler转换该字段。

  2. org.apache.ibatis.type.DateTypeHandler的获取

    9d3283bdb193f7fba32cc9ce55281e25.png
    在这里插入图片描述

    点击调用栈的红框出,跳转到rsorg.apache.ibatis.type.DateTypeHandler的获取处。

    ea7bc78fadda752bbce72d0a5618a119.png
    在这里插入图片描述

    createUsingConstructor方法这里既取到了typeHandler,又获取到了rs
    我们可以推断出,应该是通过columnNameparameterType获取typeHandler出错了。
    我们看看这里的代码:

    1  for(int i = 0; i 2    Class> parameterType = constructor.getParameterTypes()[i];
    3    String columnName = (String)rsw.getColumnNames().get(i);
    4    TypeHandler> typeHandler = rsw.getTypeHandler(parameterType, columnName);
    5    Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
    6    ...
    7  }

    看看parameterType,columnName,typeHandler三个值的类型:

    c6977b158611c06ea6d51cbee0cd58c9.png
    在这里插入图片描述

    ok,这几个值都吻合了,根据代码1-3行我们看出parameterTypecolumnName没对上导致的。这里可以推断出constructor出现了问题,为啥不怀疑是columnName出现了问题了呢?因为rsw是获取到sql返回结果构造的,是mybatis的代码,大概率不会出现问题。

  3. constructor分析

    这是constructorparameterTypes:

    70dd75c639b4bdd10b649e3e9815a57f.png
    在这里插入图片描述

    这里出现了4个字段,但是没有id, gmtUpdate,gmtCreate这几个字段,而看看rswcolumnNames的值:

    74d73b44a8b1041cda6dc6a2be9281cd.png
    在这里插入图片描述

    这里却多了id, gmtUpdate,gmtCreate这几列,问题有进一步定位到了,原来是构造器的字段和rswcolumnNames不一一对应导致的。可以从上面代码for循环得知,都是根据索引一一获取,这里个数都对不上,肯定有问题了。
     在这里基本上已经定位到问题了,就是构造器只接受了3个参数导致的。

  4. 如何获取到的constructor

    把方法栈在往上移一个,就能找到constructor的具体获取处:

    d2635c44858ca9108d9739997fe53148.png
    在这里插入图片描述

    resultType就是我的实体类,defaultConstructor通过findDefaultConstructor获取到,这个方法就不细看了,里面逻辑就是:如果只有一个构造器,那就使用该构造器,获取寻找被标记了AutomapConstructor注解的构造器。由于@Data注解只能生成一个构造器OutputValueDO(java.math.BigDecimal,java.math.BigDecimal,java.math.BigDecimal,java.util.Date),没有id, gmtUpdate,gmtCreate
    此时就有一个解决方案了,去掉@Data,自己写一个完整的构造器,包括继承的所有字段,但是这样是不太好的,从上面for循环代码可知,要数据库的表的列的顺序要和实体类的构造器的参数的顺序一致,不然还是出现问题。那就在往上个方法栈看看为什么使用了该处理逻辑,印象中的mybatis没这么坑。

  5. 更优雅的解决方案

    再往上到上一个方法栈,   createResultObject方法就找到了正主。

    9b6a87832da6d6e44062c9300efca253.png
    在这里插入图片描述

    由于!resultType.isInterface() && !metaType.hasDefaultConstructor()判断为真就走到了通过构造器反射得到结果的逻辑。接下来分别分析这个 if...else...

  • hasTypeHandlerForResultObject明显我们没有定义自己的typeHandler,故忽略

  • constructorMappings表示在mapper.xml中定义了实体类的字段和表字段的映射关系,但是我们没有定义,忽略

  • 我的实体类OutputValueDO不是接口,并且没有无参构造器,我的代码正好适合这个判断,所以我要想办法是这个判断为假。

  • 最后一个就是默认的,我要改成走到这个方法来。

    解决方案方向:加个默认构造器。加@NoArgsConstructor即可解决,该注解就能生成无参构造器,由于我加了@Builder,所以我还必须加@AllArgsConstructor,稳了,解决了。

三、总结

我一般使用lombok最好加上这几个注解:

1@Data
2@Builder
3@AllArgsConstructor
4@NoArgsConstructor

但是这个代码一开始不是我写的,而且该实体类没有继承的话也不会出问题,巧了。以前也看过mybatis的代码,但是没有翻过映射这块。今天再一次体味到了mybatis的代码:真正牛的代码不需要注释。很容易就找到了问题。

  • 我的微信:

    8e125359714c69516e906dbb7f379a47.png
    在这里插入图片描述
  • 我的微信公众号:

    76eb7443846dbfad91520a0552126bec.png
    云原生玩码部落
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值