ResultSetMetaData.getColumnName踩坑记

问题

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

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
	</parent>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.0.7</version>
		</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,发现有一段类似下面的代码:

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;

public class DefaultRowMapper implements RowMapper<Map<String, String>> {
	
	private static final Logger logger = LoggerFactory.getLogger(DefaultRowMapper.class);
	
	private String[] columnNames = null;
	private int[] columnTypes = null;
	private final SimpleDateFormat dateFromat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private final SimpleDateFormat dateFromat2 = new SimpleDateFormat("yyyy-MM-dd");

	public Map<String, String> mapRow(ResultSet rs, int rowNum) throws SQLException {
		int size = 0;
		if(columnNames == null){
			ResultSetMetaData metaData = rs.getMetaData();
			size = metaData.getColumnCount();
			columnNames = new String[size];
			columnTypes = new int[size];
			for(int i=0; i<size; i++){
				columnNames[i] = metaData.getColumnName(i+1);
				columnTypes[i] = metaData.getColumnType(i+1);
			}
		}
		Map<String, String> map = new HashMap<String, String>();
		size = columnNames.length;
		for(int i=0; i<size; i++){
			map.put(columnNames[i], getValue(rs.getObject(i+1), columnTypes[i]));
		}
		return map;
	}
	
	private  String getValue(Object fieldValue, int sqlType) {
		if(fieldValue == null){
			return null;
		}
		if(fieldValue instanceof String){
			return (String)fieldValue;
		}
		switch(sqlType){
			case java.sql.Types.TIMESTAMP:
				try {
					return dateFromat1.format(dateFromat1.parse(fieldValue.toString()));
				} catch (Exception e) {
					logger.error("dateFromat1 error"+ExceptionUtils.getFullStackTrace(e));
				}
			case java.sql.Types.DATE:
				try {
					return dateFromat2.format(dateFromat2.parse(fieldValue.toString()));
				} catch (Exception e) {
					logger.error("dateFromat2 error"+ExceptionUtils.getFullStackTrace(e));
				}
			case java.sql.Types.BOOLEAN:
			case java.sql.Types.BIT:
				if(Boolean.TRUE.equals(fieldValue)){
					return "1";
				}else{
					return "0";
				}
			default:
				return fieldValue.toString();
		}
	}

}

 (大家看到这样的代码先不要笑!毕竟是十几年前的人写的,可能那时没有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()方法来取得结果集列名。而我这个后来者,一不小心就踩到这个坑里去了。悲呼!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值