alibaba / p3c

Alibaba Java Coding Guidelines pmd implements and IDE plugin
https://github.com/alibaba/p3c/wiki
Apache License 2.0
30.28k stars 8.05k forks source link

java反射调用及泛型擦除引起的mySql索引失效 #951

Open ttt233 opened 1 year ago

ttt233 commented 1 year ago

Rule content

【强制】在mybatis sql中,查询条件从泛型集合,例如List、HashMap里取参数时,#{}里必须传javaType,避免数据库隐式转换带来的索引失效等不可预期影响。 正例:

<select id="" parameterType="java.util.List" resultMap="">
  select  u_id, created_ts
  from t_unify_user where u_id in
  <foreach collection="uids" item="item" open="(" separator="," close=")">
    <!-- 这里加入了javaType=String 约束 -->
    #{item, javaType=String}
  </foreach>
</select>

反例:

<select id="" parameterType="java.util.List" resultMap="">
  select  u_id, biz_type
  from t_unify_user where u_id in
  <foreach collection="uids" item="item" open="(" separator="," close=")">
     <!-- 这里没有加入javaType=String 约束 -->
    #{item}
  </foreach>
</select>

Problem description

上述sql对应的mapper接口为:List<User> queryUserByUidList(@Param("uids")List<String> uids) 该接口正常调用没有问题,但由于Java中的泛型是 ‘伪泛型’,程序编译期间会将泛型信息擦除而转变为非泛型类,例如List<String>List<Integer>在编译后擦除了泛型类型只留下了原始类java.util.List,对于上述的mapper接口,编译后它的入参并不区分List<String>List<Integer>,只是在编写代码时,编译器会做语法检查并拦截。

在现实服务场景中,我们会使用dubbo、hsf的泛化调用功能,服务端会根据传入的接口全限定名,参数列表等信息获取服务句柄,然后进行反射调用,这里的反射调用就会绕过上面所说的编译器拦截,例如可以传入List<Long>调用上面的mapper接口,由于数据表uid字段设置的是varchar型,传入long值查询时,数据库会发生隐式转换,进而引起索引失效,当表数据量庞大时,会timeout,带来生产问题。

实际线上问题及排查见:https://blog.csdn.net/weixin_45047729/article/details/128103900

大多数人认为在sql中指定jdbcType后,mybatis会按照jdbcType进行类型转换,再操作数据库,其实不然,官网给出的定义是: 20632f25816a4d26bee3a61be6d05e1b jdbcType适用于insert、update、delete操作可空字段的场景;而javaType适用于与HashMap等泛型集合类映射的场景,显式指定javaType可保障符合预期的行为。 原文见:mybatis – MyBatis 3 | Mapper XML Files

Advice

建议在mybatis sql中,查询条件从泛型集合,例如List、HashMap里取参数时,#{}里必须传javaType,避免数据库隐式转换带来的索引失效等不可预期影响。