深入探讨JDBC往MySQL中插入Timestamp类型字段报错问题
背景描述
最近处于工作需要,用到了Apache的commons dbutils包来操作数据库。在保存数据到MySQL数据库时,报了一个空指针异常,作为开发多年的Java老鸟,最不怕的就是空指针异常,于是打上断点开始Debug,发现报错那行代码对象、传入方法中的参数竟然都不为空。。。
问题描述
不废话,直接上代码
问题代码
@Test
public void testDbutilsDateType() {Connection connection = DBUtils.getConnection();String sql = "insert into test (username, nickname, password, createtime) values (?,?,?,?)";QueryRunner qr = new QueryRunner();try {// 报错的48行int effectRows = qr.update(connection, sql, "lisi", "李四", "456", new Timestamp(System.currentTimeMillis()));System.out.println("影响行数:" + effectRows);} catch (SQLException e) {e.printStackTrace();} finally {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}
}
报错信息
java.lang.NullPointerExceptionat com.mysql.jdbc.PreparedStatement.setTimestamp(PreparedStatement.java:4241)at com.mysql.jdbc.PreparedStatement.setObject(PreparedStatement.java:3591)at com.mysql.jdbc.JDBC42PreparedStatement.setObject(JDBC42PreparedStatement.java:68)at org.apache.commons.dbutils.AbstractQueryRunner.fillStatement(AbstractQueryRunner.java:374)at org.apache.commons.dbutils.QueryRunner.update(QueryRunner.java:527)at org.apache.commons.dbutils.QueryRunner.update(QueryRunner.java:444)at com.john.demo.jdbc.TestJDBC.testDbutilsDateType(TestJDBC.java:48)
排查步骤
首先我把报错的第48行代码做了排查
int effectRows = qr.update(connection, sql, "lisi", "李四", "456", new Timestamp(System.currentTimeMillis()));
但是qr、connection都不是null
跟踪dbutils、mysql驱动包源码
发现dbutils中在填充参数时做了一个操作,使用了prepareStatement.getParameterMetaData()
方法来获取传入参数在数据库中对应的元数据信息。而返回的ParameterMetaData
对象中的metadata.fields
属性是null,但是在5.1.47的驱动包中并没有做空值检查,所以就炸了关键代码栈:
dbutils部分- QueryRunner类的527行: this.fillStatement(stmt, params);
- AbstractQueryRunner#fillStatement方法341行:pmd = stmt.getParameterMetaData();
- AbstractQueryRunner#fillStatement方法374行:stmt.setObject(i + 1, params[i]);
mysql 5.1.47驱动包部分
- com.mysql.jdbc.PreparedStatement#setTimestamp(int, java.sql.Timestamp):
public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException {synchronized (checkClosed().getConnectionMutex()) {int fractLen = -1;if (!this.sendFractionalSeconds || !this.serverSupportsFracSecs) {fractLen = 0;// 报错的代码: this.parameterMetaData.metadata.fields为空,没有做空值检查} else if (this.parameterMetaData != null && parameterIndex <= this.parameterMetaData.metadata.fields.length && parameterIndex >= 0) {fractLen = this.parameterMetaData.metadata.getField(parameterIndex).getDecimals();}setTimestampInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false, fractLen,this.connection.getUseSSPSCompatibleTimezoneShift());}}
至此,问题原因算是找到了,上google搜索一下国外的大神们怎么说,在mysql官方论坛上找到了一些线索:MySQL官方论坛Bug#92089
原来这是MySQL5.1.47版本的驱动包的Bug,国外好多道友也遇到了,有个叫Kevin的哥们还说回滚到5.1.46版本问题就解决了,换成5.1.46版本驱动包尝试了一把,果然解决了!
话外
到这里问题已经解决了,但还是有个疑问,既然是驱动包的Bug,那用原生JDBC的代码是不是也有问题?
于是马上写了个测试,代码如下:
@Test
public void testJdbcDateType() {Connection connection = DBUtils.getConnection();String sql = "insert into test (username, nickname, password, createtime) values (?,?,?,?)";PreparedStatement pstmt = null;try {pstmt = connection.prepareStatement(sql);pstmt.setString(1, "zhangsan");pstmt.setString(2, "张三");pstmt.setString(3, "123");pstmt.setObject(4, new Timestamp(System.currentTimeMillis()));int effectRows = pstmt.executeUpdate();System.out.println("影响行数:" + effectRows);} catch (SQLException e) {e.printStackTrace();} finally {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}
}
发现即使使用MySQL5.1.47的驱动包,依然没有任何问题,但是如果你调用了prepareStatement.getParameterMetaData()
方法就会炸:
@Test
public void testJdbcDateType() {Connection connection = DBUtils.getConnection();String sql = "insert into test (username, nickname, password, createtime) values (?,?,?,?)";PreparedStatement pstmt = null;try {pstmt = connection.prepareStatement(sql);// 只要写上这行,就会报错,不写就正常ParameterMetaData data = pstmt.getParameterMetaData();pstmt.setString(1, "zhangsan");pstmt.setString(2, "张三");pstmt.setString(3, "123");pstmt.setObject(4, new Timestamp(System.currentTimeMillis()));int effectRows = pstmt.executeUpdate();System.out.println("影响行数:" + effectRows);} catch (SQLException e) {e.printStackTrace();} finally {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}
}
再进MySQL驱动包中排查,发现还是一样的问题:
MySQL驱动包中对标准JDBC接口preparedStatement.setObject
方法的实现中,如果参数是TimeStamp类型,会在setTimeStamp
方法中进行如下判断:
public void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar cal) throws SQLException {synchronized (checkClosed().getConnectionMutex()) {int fractLen = -1;if (!this.sendFractionalSeconds || !this.serverSupportsFracSecs) {fractLen = 0;// 注意这行} else if (this.parameterMetaData != null && parameterIndex <= this.parameterMetaData.metadata.fields.length && parameterIndex >= 0&& this.parameterMetaData.metadata.getField(parameterIndex).getDecimals() > 0) {fractLen = this.parameterMetaData.metadata.getField(parameterIndex).getDecimals();}setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true, fractLen, this.connection.getUseSSPSCompatibleTimezoneShift());}
}
至此,问题彻底水落石出了,跟Apache的dbutils无关,跟你用什么连接池也无关,只不过dbutils中恰好用到了5.1.47驱动包中有bug的方法,替驱动包背锅了~~
让我们来看看5.1.46版本的驱动包中的PreparedStatement
类中的setTimeStamp
方法的实现:
public void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar cal) throws SQLException {synchronized (checkClosed().getConnectionMutex()) {// 简单粗暴,直接没有判断,管你有没有获取到ParameterMetaData,都是同样处理!!!setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true);}
}
结论
当我们使用dbutils往MySQL中插入TimeStamp
类型出现空指针异常,而你确定空指针不是由于你的外部代码导致时,果断看看你MySQL驱动包的版本是不是用的坑爹的5.1.47!!!
深入探讨JDBC往MySQL中插入Timestamp类型字段报错问题相关推荐
- 迁移数据时 timestamp类型字段报错: 1067 - Invalid default value for 'login_time'
MySQL数据库升级 8.0.13,原版本5.5:执行导出来的SQL文件时报错 1067 - Invalid default value for 'login_time' 原因:MySQL 5.6以后 ...
- JDBC向数据库中插入BLOB类型数据
目录 1.Blob介绍 2.使用PreparedStatement向数据表中插入Blob类型字段 3.从数据表中读取Blob类型数据 4.特殊情况说明 1.Blob介绍 Blob是一种二进制数据文件, ...
- Java如何给Mysql中插入year类型数据
文章目录 Java如何给Mysql中插入year类型数据 实际问题: 解决方案: 从数据库中读取year类型数据到Java 从Java中往数据库存储year数据 Java如何给Mysql中插入year ...
- 公众号开发-群发图文中插入小程序卡片报错 invalid content hint 的解决
如果你也正在开发群发图文中插入小程序的功能,那么大概率也会遇到这问题. 之前通过微信第三方开放平台开发过一个可以管理多个公众号的系统,具体功能和效果可以参考 微信第三方开放平台代公众号实现业务 . 为 ...
- python mysql批量insert数据_使用python往mysql批量插入数据时,报错not all arguments converted...
我用这段命令可以往mysql数据库插入数据 insert into moderation_task(id, media_id, user_id, media_url_or_path, media_ti ...
- ibatis mysql 同时删多个表报错_MySQL中Multiple primary key defined报错的解决办法
MySQL中Multiple primary key defined报错的解决办法 创建主键可以有两种方式: create table 表名( 字段名 类型, 字段名 类型, -- primary k ...
- Mysql存储过程老是报错_mysql中看看这个存储过程老是报错,该如何处理
mysql中看看这个存储过程老是报错 我的mysql版本是5.5.21的,下面这个存储过程是需要更加另外3张表的数据来更新strategycontracttemp中数据,但是每次更新到中途报错,先代码 ...
- mysql怎么插入时间_如何在MySQL中插入日期?
在MySQL中插入日期的方法:首先打开脚本文件:然后通过[INSERT INTO tablename (col_name, col_date) VALUE ('DATE: Auto CURDATE() ...
- python向数据库写入数据_如何用Python向Mysql中插入数据
我们使用Python经常会和Postgresql进行搭配,很少将python和mysql进行搭配.下面小编给大家分享如何用Python向Mysql中插入数据. 工具/原料 Pycharm 方法/步骤 ...
最新文章
- c语言 将url图片存到本地_一个22万张NSFW图片的鉴黄数据集?我有个大胆的想法……...
- mysql 不同的文件系统_文件系统,数据库管理系统,操作系统之间有什么联系?...
- 从源码透析gRPC调用原理
- Ubuntu无法正常输入英文单引号符号 + 误删除package导致系统设置异常(解决方案)...
- Windows没有关机按钮 如何使用命令行关机 重启
- 【转】Java 项目UML反向工程转化工具
- 树莓派2代干货帖(第一天)按图索骥的搭建
- Python面试必备!最全面的重点知识汇总,建议收藏!
- bzoj 4815: [Cqoi2017]小Q的表格
- c语言魂斗罗小游戏代码,(搬运)魂斗罗系列游戏秘籍(包含一些解锁要数)
- lua 函数 默认值_简明lua教程
- 什么是全栈工程师,如何成为全栈工程师
- 【工作笔记】微信公众号页面摇一摇+触发音效
- 2020年最好用的手机是哪一款_2020年什么手机好用,买手机建议买什么牌子的?...
- 傅里叶变换和逆变换公式的我理解意义
- re的剩余模块和subprocess模块
- 命令可以在linux的安全系统中,什么命令可以在linux的安全系统中完成文件向磁带备份的工作...
- 正则表达式(二)常用正则表达式——验证邮箱
- Meterpreter渗透测试入门
- Webdings,Wingdings图形字体对照表
热门文章
- MSN用户免费领取卡巴斯基反病毒软件2010半年激活码.
- 免费,开源的数字货币收款插件
- 擦亮老字号!遵义产区助力酒企高质量发展再出新举措
- 宝贵的SCROLL LOCK键?!没有它如何强制Windows蓝屏?
- PyQt5实现简易音乐播放器
- php 身份认证 claim,asp.net core cookie身份认证view视图中读取/读取User.Claims中的值实例...
- Claims如何获取里面的信息
- Navicat窗口很大怎么解决
- android中的数字签名技术
- L293D电机驱动版在Arduino上使用笔记