理解 MyBatis JdbcType

背景

有时候,在使用 MyBatis 时会报下面的错误:

Error setting null for parameter #6 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 无效的列类型:1111
; uncategorized SQLException for SQL []; SQL state [99999]; error code [17004]; 无效的列类型:1111; nested exception is java.sql.SQLException: 无效的列类型:1111

错误通常发生在传给 Insert 或 Update 语句的参数为 null 的时候。比如:

<insert id="insertCustomerLog" parameterType="map">  
    insert into customer_log
    (
        CUSTOMER_SERVICE_USER_NAME,
        user_name,
        CONTENT,
        LOG_FIRST_TYPE,
        STATUS,
        LINKED_ID,
        FEE,
        ACCOUNT_FIRST_TYPE,
        ACCOUNT_SECOND_TYPE,
        ACCOUNT_THIRD_TYPE,
        LOG_SECOND_TYPE,
        LOG_IP,
        MEMO
    )
    values
    (
        #{customerServiceUserName},
        #{username},
        #{content},
        #{logFirstType},
        #{status},
        #{linkedId},
        #{fee},
        #{accountFirstType},
        #{accountSecondType},
        #{accountThirdType},
        #{logSecondType},
        #{logIp},
        #{memo}
    )
</insert>

#{logIp}等其他参数为 null 时,就会报上面的错误。通常修复的方式是明确指定#{logIp}等参数的 JdbcType:

<insert id="insertCustomerLog1" parameterType="com.diyicai.customer.domain.CustomerLog">  
    insert into customer_log
    (
        CUSTOMER_SERVICE_USER_NAME,
        user_name,
        CONTENT,
        LOG_FIRST_TYPE,
        STATUS,
        LINKED_ID,
        FEE,
        ACCOUNT_FIRST_TYPE,
        ACCOUNT_SECOND_TYPE,
        ACCOUNT_THIRD_TYPE,
        LOG_SECOND_TYPE,
        LOG_IP,
        MEMO
    )
    values
    (
        #{customerServiceUserName,jdbcType=VARCHAR},
        #{username,jdbcType=VARCHAR},
        #{content,jdbcType=VARCHAR},
        #{logFirstType,jdbcType=NUMERIC},
        #{status,jdbcType=NUMERIC},
        #{linkedId,jdbcType=VARCHAR},
        #{fee,jdbcType=NUMERIC},
        #{accountFirstType,jdbcType=NUMERIC},
        #{accountSecondType,jdbcType=NUMERIC},
        #{accountThirdType,jdbcType=NUMERIC},
        #{logSecondType,jdbcType=NUMERIC},
        #{logIp,jdbcType=VARCHAR},
        #{memo,jdbcType=VARCHAR}
    )
</insert>

分析

MyBatis 底层,由 TypeHandler 的 setParameter 方法将 Mapper 方法参数中的值赋值给 PreparedStatement,不同的类型由不同的 TypeHandler 实现处理,比如 IntegerTypeHandler、StringTypeHandler 等,StringTypeHandler 等常用 TypeHandler 都继承自 BaseTypeHandler,BaseTypeHandler 中实现了 setParameter 方法:

@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
  if (parameter == null) {
    if (jdbcType == null) {
      throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
    }
    try {
      ps.setNull(i, jdbcType.TYPE_CODE);
    } catch (SQLException e) {
      throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
              "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
              "Cause: " + e, e);
    }
  } else {
    try {
      setNonNullParameter(ps, i, parameter, jdbcType);
    } catch (Exception e) {
      throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
              "Try setting a different JdbcType for this parameter or a different configuration property. " +
              "Cause: " + e, e);
    }
  }
}

可以看到这是一个模板方法,对 setParameter 参数 parameter 为 null 的情况做了统一处理,子类只需要重写 setNonNullParameter 方法即可。而前面提到的报错就是在ps.setNull(i, jdbcType.TYPE_CODE)报错时抛出的。

之所以ps.setNull(i, jdbcType.TYPE_CODE)报错,是因为大多数数据库驱动在设置 null 时,需要指定 JdbcType 的类型。有的驱动需要 JdbcType 和列的类型一致,而有的驱动需要设置为指定类型比如 NULL、VARCHAR 或 OTHER。MyBatis 默认使用的是 OTHER,而数据库驱动不支持,所以报错。

MyBatis 支持 JdbcTypeForNull 配置,用来指定当参数为 null 时,设置 JdbcType 的类型,比如对于 Oracle 10g,可将其设置为 NULL:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="jdbcTypeForNull" value="NULL" />
    </settings>
</configuration>

参考:

mybatis 需要注意的点 MyBatis 插入空值时,需要指定 JdbcType

MyBatis 的 jdbcType 的作用

MyBatis 3 | Configuration – mybatis

java - MyBatis - jdbcTypeForNull Oracle

MyBatis-Error setting null parameter

posted @ 2024-12-26 23:28  Higurashi-kagome  阅读(211)  评论(0)    收藏  举报