写点什么

JDBC 类型解析

作者:豆芽发达咯
  • 2024-08-29
    陕西
  • 本文字数:2579 字

    阅读完需:约 8 分钟

JDBC类型解析

有个程序本来是在 Sqlserver 下运行的,写的 SQL 也是标准的 T-SQL 语句,没有特殊语法。某一天切换成了 Oracle,本来信心满满认为那指定不会有问题,又没什么特殊语法嘛,但是刚切换执行,登录就报错了 o(╯□╰)o .....错误信息显示竟然是一个简单的 select count(1) from x_table where lname='xx'报错了。java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.Integer


异常具体情形复现

根据控制台的错误堆栈信息,很容易就能定位到错误,并且根据代码行数追踪到具体的代码

//异常信息:java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.Integer	at com.loo.bd.pub.service.RegService.cancelCc(RegService.java:284)	at com.loo.bd.pub.service.RegService$$FastClassBySpringCGLIB$$5b3e2d7e.invoke(<generated>)
//代码片段:Mybatis的接口和对应的xml Map<String,Integer> queryLoginLogTimes();//一个很简单的查询语句 <select id="queryLoginLogTimes" parameterType="java.lang.String" resultType="java.util.Map"> select count(1) times,max(login_date) last_date from user_log where pk_user = #{pkUser,JdbcType=VARCHAR} </select> //service部分代码如下 Map<String,Object> userLogMap = userMapper.queryLoginLogTimes(login.getPkuser()); if(userLogMap!=null && userLogMap.size()>0){ Integer currentTimes = Integer(userLogMap.get("times"));//:284行就这里哦 }
复制代码


原因分析和解决问题

对于 count 函数的返回类型不同

在 SQLServer 中 count 函数的返回数据类型是 int,在 Oracle 中 count 函数返回的类型是 Number。而对应 JDBC 驱动的实现中,映射 Java 的数据类型一个就是 int,一个则是 BigDecimal。

编码不规范导致驱动按照默认类型处理

上面的编码实例中,没有使用 Mybatis 的 ResultMap 映射结果集,而是直接使用了一个 Map 对象来接受,这样依赖底层处理类型接受,读取列的类型映射关系完全按照 jdbc 驱动包的实现来处理。而每一个厂商实现的驱动包默认的类型就会有所不同。

正确的姿势:
  1. 要么使用 ResultMap 对结果集做映射,让 Mybatis 读取数据的时候按照指定类型读取(其实就是jdbc中的ResultSet中的getXXX方法,按照不同类型读取)

  <resultMap id="loginTimeMap" type="com.loo.bd.LoginLog">      <result column="times" property="times" javaType="Integer"/>      <result column="last_date" property="lastDate" javaType="Date" />  </resultMap>  <select id="queryLoginLogTimes" parameterType="java.lang.String" resultMap="loginTimeMap">			select count(1) times,max(login_date) last_date from user_log where pk_user = #{pkUser,JdbcType=VARCHAR}	</select
复制代码
  1. 当然如果,考虑返回的是一个值,比如上面的语句,如果只是返回一个 count 统计,可以直接指定 resultType="int"即可。这样无论在哪个数据库下返回也是 int,毕竟单个 count 永远不会返回 null

  2. 当然使用 MybatisPlus 也行,或者其他持久化映射框架,总之不要返回一个 map,尤其是返回一个类型不确定的结果,都有问题。

错误姿势和理解:

有人可能会想,那我如果确定返回类型了,比如确定所有列返回都是 int 了,我直接采用 Map<String,Integer>在 Mapper 接口中定义接受不就完了。

Map<String,Integer> userLogMap = userMapper.queryLoginLogTimes(login.getPkuser());
复制代码

像这样这样编译是不会报错,但是运行依然是类型转换异常。Mybatis 的底层运行时返回的其实是 Object 类型,最终返回的时候只是判断 目标类型是不是返回结果类型或者其接口、超类类型。也就是并不关注具体的泛型类型,具体泛型类型的准确性是需要程序员自己判定和映射。这里的处理姿势与 Java 是弱泛型有关系,Java 的泛型是运行时擦除的,简单说就是虽然我们定义的是 Map<String,Integer>但实际上,运行时就是个 Map.如下是 Mybatis 底层对于类型的判断:

// 类似上面使用到的查询,具体方法会走到MapperMethod中的executeForMany判断类型能匹配,并不关注泛型类型。    if (!method.getReturnType().isAssignableFrom(result.getClass())) {      if (method.getReturnType().isArray()) {        return convertToArray(result);      } else {        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);      }    }
复制代码


默认类型到底从何而来

对于 Java 程序而言,大多情况下访问数据库都是通过各个厂商按照 jdbc 规范自己实现的驱动 jar 来访问和操作具体的数据库,那么这些数据类型,以及数据库数据类型和 Java 数据类型的映射关系都是在驱动包中处理的。

各驱动包依据 jdbc 规范中ResultSetMetaData接口,实现自己的元数据处理类,其中就包含了返回列的‘数据库数据类型’、‘Java 数据类型’,以及列名称、大小等等都在此接口的实现中。比如 Oracle 对于数据类型映射的实现,对应的数据库类型映射是实现规范接口的getColumnTypeName方法,可以看到 6 代表的是“Number”<>"java.math.BigDecimal"之间映射。

一个获取列数据类型,默认映射的代码:

try(Connection conn = getConnection();            PreparedStatement pstmt = conn.prepareStatement("select * from TEST_TB");            ResultSet resultSet = pstmt.executeQuery()){            ResultSetMetaData metaData = resultSet.getMetaData();            while (resultSet.next()){                for (int i = 1; i <= metaData.getColumnCount(); i++) {                    System.out.println(metaData.getColumnName(i) + "\t" +metaData.getColumnTypeName(i)+ "\t" +metaData.getColumnDisplaySize(i) +"\t" + resultSet.getObject(i).getClass());                }            }        }
复制代码

总结

通常情况下,我们采用一些持久层框架,规范化代码实现,都会指定具体的数据类型,避免了以上问题的发生,因此大多人对此类问题并无感知,也不知道为何会如此。(工具的好处和坏处!)

使用框架包装,不指定具体类型,就会按照 Object 类型去读去处理,而按照 Object 类型,就会使用到驱动层面实现的默认类型映射。

比如 Oracle 对于 int、Number 类型的映射都是 java.math.BigDeciml、Sqlserver 对于 int 类型映射为 Integer,numeric 映射为 java.math.BigDecimal

发布于: 刚刚阅读数: 3
用户头像

从哪里跌倒,在哪里休息一会,孟起会再倒下 2019-05-29 加入

还未添加个人简介

评论

发布
暂无评论
JDBC类型解析_JDBC_豆芽发达咯_InfoQ写作社区