MybatisPlus更新null到datetime2字段报错:不允许从数据类型 varbinary 到 datetime2 的隐式转换
背景
今日做开发,数据库使用了SQLServer,expiredTime的值为null时更新报错了,其字段类型为datetime2:

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 不允许从数据类型 varbinary 到 datetime2 的隐式转换。请使用 CONVERT 函数来运行此查询。
解决方案
目前发现两种解决方案,这两种方法都是类似的。
方法一 指定mapping
不用写JdbcType.,直接写枚举的名称,比如TIMESTAMP
lambdaUpdate()
.eq(ShortLink::getId, id)
.set(ShortLink::getExpiredTime, req.getExpiredTime(), "jdbcType=TIMESTAMP")
.set(ShortLink::getStatus, req.getStatus())
.update();
方法二 在XML里写jdbcType
<update id="updateData">
UPDATE table
SET
ExpiredTime = #{req.expiredTime,jdbcType=TIMESTAMP}
WHERE Id = #{id}
</update>
还有一种方法就是把数据库的字段类型设置成datetime,这样更新就不会报错。
网上说的TableField注解里加jdbcType或者typeHandler是没用的,这两个只对查询后的结果有效。
根本原因
根本原因是在更新数据时,因为值为null,jdbcType被自动设置为了JdbcType.OTHER,同时可能因为SQLServer的datetime2没有做自动转换,导致报错。
我们都知道,执行SQL会使用PreparedStatement,然后通过set设置参数的值。
SQLServer的驱动有它自己的PreparedStatement,SQLServerPreparedStatement。
在SQLServerPreparedStatement的setNull方法里打一个断点,就能看到jdbcType的值是1111,对应就是JdbcType.OTHER。
public final void setNull(int index, int jdbcType) throws SQLServerException {
if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {index, jdbcType});
checkClosed();
setObject(setterGetParam(index), null, JavaType.OBJECT, JDBCType.of(jdbcType), null, null, false, index, null);
loggerExternal.exiting(getClassNameLogging(), "setNull");
}

那这个jdbcType又来自哪?来自DefaultParameterHandler

这里可以通过配置来改变jdbcTypeForNull的值,配置成TIMESTAMP是可以的,但是可能对其他字段有影响,不推荐,配置成NULL一样的问题。(如果要配置成NULL不要写成小写的null,否则真的会是null,或者外面加双引号)
mybatis-plus:
configuration:
jdbc-type-for-null: TIMESTAMP
上面是jdbcType和值都为null的情况,一番DEBUG后发现,jdbcType的值来自于parameterMappings。
SqlSourceBuilder.ParameterMappingTokenHandler.buildParameterMapping(String content)方法。
这个方法会解析传入的content,也就是#{}中间的值,然后根据值去设置jdbcType等属性。
这里可以看到,并没有指定jdbcType。

如果指定了,值会是这样的:

有没有办法加上jdbcType呢?查看mybatis-plus的源码,发现需要传入一个叫mapping的参数。

默认情况下mapping会是null。

浙公网安备 33010602011771号