• ResultSetMetaData.getColumnName踩坑记


    问题

    一个年代久远的项目:将商品库的数据清洗扩展,发送至消息中心,再写入ElasticSearch。某一次新需求开发。看到项目中的POM.xml文件有告警信息:

    1. <parent>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-parentartifactId>
    4. <version>1.5.2.RELEASEversion>
    5. parent>
    6. <dependency>
    7. <groupId>mysqlgroupId>
    8. <artifactId>mysql-connector-javaartifactId>
    9. <version>5.0.7version>
    10. dependency>

    Overriding managed version 5.1.41 for mysql-connector-java

     嗯,这个mysql 的JDBC驱动版本为啥用这么低的版本,不科学!去了,让它和SpringBoot保持一致。于是,我去除了version指定,愉快地发版了。结果半夜被同事叫醒,说我负责的ElasticSearch数据有问题。经过调查,发现有问题的地方与这次新需求的改动毫无关系。我坚决及肯定地认为不是我的问题(当然我私下又把自己的代码审视了一番之后保证的)。但问题总是要解决,只好十分不情愿地回退版本。结果神奇地发现,数据在渐渐变得正确。

    嗯,看来,确实是这次的改动造成了这次“动荡”。

    第二天,再次仔细对比了版本,发现除了新需求改动,我还动了上面的POM文件。经过两次打包文件的对比,发现新版本的依赖包:mysql-connector-java已经是5.1.41。

    升级JDBC驱动包会影响业务逻辑?!这很不科学!

    经过很长时间的本地DEBUG,发现有一段类似下面的代码:

    1. import java.sql.ResultSet;
    2. import java.sql.ResultSetMetaData;
    3. import java.sql.SQLException;
    4. import java.text.SimpleDateFormat;
    5. import java.util.HashMap;
    6. import java.util.Map;
    7. import org.apache.commons.lang.exception.ExceptionUtils;
    8. import org.slf4j.Logger;
    9. import org.slf4j.LoggerFactory;
    10. import org.springframework.jdbc.core.RowMapper;
    11. public class DefaultRowMapper implements RowMapper> {
    12. private static final Logger logger = LoggerFactory.getLogger(DefaultRowMapper.class);
    13. private String[] columnNames = null;
    14. private int[] columnTypes = null;
    15. private final SimpleDateFormat dateFromat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    16. private final SimpleDateFormat dateFromat2 = new SimpleDateFormat("yyyy-MM-dd");
    17. public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
    18. int size = 0;
    19. if(columnNames == null){
    20. ResultSetMetaData metaData = rs.getMetaData();
    21. size = metaData.getColumnCount();
    22. columnNames = new String[size];
    23. columnTypes = new int[size];
    24. for(int i=0; i
    25. columnNames[i] = metaData.getColumnName(i+1);
    26. columnTypes[i] = metaData.getColumnType(i+1);
    27. }
    28. }
    29. Map map = new HashMap();
    30. size = columnNames.length;
    31. for(int i=0; i
    32. map.put(columnNames[i], getValue(rs.getObject(i+1), columnTypes[i]));
    33. }
    34. return map;
    35. }
    36. private String getValue(Object fieldValue, int sqlType) {
    37. if(fieldValue == null){
    38. return null;
    39. }
    40. if(fieldValue instanceof String){
    41. return (String)fieldValue;
    42. }
    43. switch(sqlType){
    44. case java.sql.Types.TIMESTAMP:
    45. try {
    46. return dateFromat1.format(dateFromat1.parse(fieldValue.toString()));
    47. } catch (Exception e) {
    48. logger.error("dateFromat1 error"+ExceptionUtils.getFullStackTrace(e));
    49. }
    50. case java.sql.Types.DATE:
    51. try {
    52. return dateFromat2.format(dateFromat2.parse(fieldValue.toString()));
    53. } catch (Exception e) {
    54. logger.error("dateFromat2 error"+ExceptionUtils.getFullStackTrace(e));
    55. }
    56. case java.sql.Types.BOOLEAN:
    57. case java.sql.Types.BIT:
    58. if(Boolean.TRUE.equals(fieldValue)){
    59. return "1";
    60. }else{
    61. return "0";
    62. }
    63. default:
    64. return fieldValue.toString();
    65. }
    66. }
    67. }

     (大家看到这样的代码先不要笑!毕竟是十几年前的人写的,可能那时没有ORM,也可能那时作者不知道ORM)

    如业务有类似下面的SQL:

    SELECT PROVINCE_ID provinceId FROM PRODUCT_RANGE WHERE LIST_ID=?

    在驱动版本:5.0.7时,metaData.getColumnName得到的是"provinceId",而当驱动版本升级为5.1.14时,得到的却是"PROVINCE_ID"。这就导致新版本业务数据全部不正常。

    按照原编码的意图,应该使用metaData.getColumnLabel取得列名。但为什么原编码不使用metaData.getColumnLabel呢?我将本地环境改为了5.0.7后,发现无论是getColumnName()还是getColumnLabel()方法均返回"provinceId"。这就导致原作者认为无论使用哪个方法都是对的。

    结论

    经过不断调查,终于在官网上找到比较满意的回答。 

    MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.3.17 JDBC compliancehttps://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-jdbc-compliance.html为了防止官网改版移动位置,我在此快照下来:

    useColumnNamesInFindColumn

    在 JDBC-4.0 之前,JDBC 规范有一个错误,该错误与可以作为列名提供给结果集方法(如“findColumn()”)或采用 String 属性的 getter 相关。JDBC-4.0 将“列名”澄清为表示标签,如“AS”子句中给出并由“ResultSetMetaData.getColumnLabel()”返回,如果未指定“AS”子句,则为列名。将此属性设置为“true”将导致与 JDBC-3.0 和早期版本的 JDBC 规范一致的行为,但可能会产生意外结果。此属性优于“useOldAliasMetadataBehavior”

    默认值false
    自版本5.1.7

     

    useOldAliasMetadataBehavior

    驱动程序是否应该对列和表上的“AS”子句使用旧行为,并且只返回“ResultSetMetaData.getColumnName()”或“ResultSetMetaData.getTableName()”的别名(如果有)而不是原始列/表名?

    默认值false
    自版本5.0.4

    简单地说,5.1.7版本时就已经使用JDBC4.0规范作为默认实现了。但为了兼容前版本的调用者,驱动有个开关。

    说到底,原编码在使用5.0.7版本时,如果仔细看一下API,就应该使用getColumnLabel()方法来取得结果集列名。而我这个后来者,一不小心就踩到这个坑里去了。悲呼!

  • 相关阅读:
    【SpringBoot集成Redis + Session持久化存储到Redis】
    实战计算机网络02——物理层
    Kubernetes(K8S)命令指南
    一. HTML基础开发标签
    OBS-Studio-27.2.4-Full-Installer-x64.exe 下载
    2022 Java生态系统报告:Java 11超Java 8、Oracle在缩水、Amazon在崛起
    C语言哈希表的线性探测法
    如何使用Vue原生组件编译应用程序主题?这个工具不要错过
    Ansible--playbook剧本
    【C++】二叉搜索树
  • 原文地址:https://blog.csdn.net/wzg725/article/details/126183289