从Java ResultSet中检查null int值

330

我正在Java中尝试测试一个ResultSet返回的列被转换成原始类型int时是否为null。

int iVal;
ResultSet rs = magicallyAppearingStmt.executeQuery(query);
if (rs.next()) {
  if (rs.getObject("ID_PARENT") != null && !rs.wasNull()) {
    iVal = rs.getInt("ID_PARENT");
  }
}

从上述代码片段中,有更好的方法来做到这一点吗?我认为第二个wasNull()测试是多余的。

请给我们讲解一下,谢谢。


23
我发现这个问题是因为我在数据库中有一个可空列,并且在Java中它被表示为一个整数。你可能认为在数据库中拥有可空的数字列是很常见的,ResultSet API会更加优雅地适应它。 - spaaarky21
我不会将这个作为答案发布,因为它是离题且远非通用的:我通常的解决方案是在SELECT语句中加入IF(colName = NULL, 0, colName) AS colName(最好是在存储过程中)。从哲学上讲,这归结于数据库是否应该符合应用程序,还是反之。由于SQL轻松处理NULL,并且许多SQL消费者不处理NULL(例如java.sql.ResultSet),因此我选择尽可能在数据库中处理它。(当然,这假设在概念上,NULL和零对于您的目的是等效的。) - s.co.tt
你可以轻松地在SQL中完成这个操作,而不必担心SQL的“NULL”和Java的“null”。只需编写“... IS NOT NULL”并在JDBC中将其检索为布尔值即可。 - Tech Expert Wizard
13个回答

440
ResultSet.getInt 的默认行为是,在字段值为 NULL 时返回 0,而这也是您的 iVal 声明的默认值。在这种情况下,您的测试完全是多余的。
如果您实际上希望在字段值为 NULL 时执行其他操作,我建议:
int iVal = 0;
ResultSet rs = magicallyAppearingStmt.executeQuery(query);
if (rs.next()) {
    iVal = rs.getInt("ID_PARENT");
    if (rs.wasNull()) {
        // handle NULL field value
    }
}

(根据@martin的评论进行了编辑;原始代码无法编译,因为iVal未初始化)


6
请参考ResultSet中getInt的Javadoc:“返回:列的值;如果该值为SQL NULL,则返回值为0”。 - Cowan
215
事实上,确实有些荒谬。getInt() 应该是 getInteger(),它返回一个 Integer,如果数据库值为 null 则会返回 null。这个开发团队真的搞砸了。 - ryvantage
7
按照这种逻辑,Java 中的所有内容都应该被开发成避免 NPE,但事实并非如此。避免 NPE 是应用程序开发者的责任,而不是语言内部 API 的责任。 - Mateus Viccari
17
哦我的天啊,为什么不直接返回NULL呢?0和NULL是两个完全不同的东西。 - deFreitas
7
@ryvantage ResultSet是Java的原始类之一,当Java不支持自动展开时就存在了。此外,它是一个非常低级别的API,不应该强制将数据库中的原始字节包装在Java Integer对象中产生额外的成本。也许可以为接口添加一个getInteger方法,返回一个Integer(甚至是Optional<Integer>)的参数。 - Richard
显示剩余16条评论

112

另一种解决方案:

public class DaoTools {
    static public Integer getInteger(ResultSet rs, String strColName) throws SQLException {
        int nValue = rs.getInt(strColName);
        return rs.wasNull() ? null : nValue;
    }
}

非常易读和实用,至今仍然感谢! - Eric

37

只需使用ResultSet#getObject()检查字段是否为null。将-1替换为您想要的空值情况即可。

int foo = resultSet.getObject("foo") != null ? resultSet.getInt("foo") : -1;

或者,如果你能保证使用正确的数据库列类型,以便ResultSet#getObject()确实返回一个Integer(因此不是LongShortByte),那么你也可以将其强制转换为Integer

Integer foo = (Integer) resultSet.getObject("foo");

2
不会,但在非空情况下会构造一个不必要的整数对象。(顺便说一句,大多数JDBC驱动程序在任何ResultSet方法调用期间都不会访问数据库...通常只有在所有数据传输完毕后才会返回ResultSet)。 - EricS
它取决于获取大小。在大多数驱动程序中,默认值为10行,一旦检索到获取,它们将被处理,但在处理完成之前,下一个获取将不会被检索。 - Kristo Aun
1
我很惊讶你的答案不是更好的答案 :-) 我投票赞成,因为这是一个适当的答案,避免了非常棘手的不安全的wasNull()方法。对我来说,这是停止使用Java的补充原因 ;-) 并继续使用VB.Net,因为RecordSet已经解决了这个简单的问题超过10年! - schlebe

28

我认为它是多余的。rs.getObject("ID_PARENT") 应该返回一个 Integer 对象或者 null,如果列值实际上是 NULL。所以,甚至应该可以这样做:

if (rs.next()) {
  Integer idParent = (Integer) rs.getObject("ID_PARENT");
  if (idParent != null) {
    iVal = idParent; // works for Java 1.5+
  } else {
    // handle this case
  }      
}

6
在我的情况下,这个问题在于调用getObject方法并不一定会返回一个整数,因为我使用的Oracle数据库中列类型的特性是"Number"。 - Matt Mc
和Matt一样的问题...使用MySQL和Types.BIGINT(应该映射为Long),getObject()方法返回0而不是null。 - xonya
5
也可以使用rs.getObject("ID_PARENT", Integer.class) - Arlo

11

据我所知,您可以简单地使用

iVal = rs.getInt("ID_PARENT");
if (rs.wasNull()) {
  // do somthing interesting to handle this situation
}

即使它是NULL。


8

关于Java泛型的更新。

您可以创建一个实用程序方法来从先前转换的给定ResultSet中检索任何Java类型的可选值。

不幸的是,getObject(columnName, Class)不返回null,而是给定Java类型的默认值,因此需要进行2次调用。

public <T> T getOptionalValue(final ResultSet rs, final String columnName, final Class<T> clazz) throws SQLException {
    final T value = rs.getObject(columnName, clazz);
    return rs.wasNull() ? null : value;
}

在这个例子中,你的代码可能如下所示:
final Integer columnValue = getOptionalValue(rs, Integer.class);
if (columnValue == null) {
    //null handling
} else {
    //use int value of columnValue with autoboxing
}

很高兴收到反馈


有趣。如果你调用 getObject(),它返回一个引用类型,它已经可以回答 null。在 v11 的 Javadoc 中说它对 SQL NULL 回答 null。除非你有一个不寻常的驱动程序或数据类型回答某种非空的“空”对象,否则我期望 wasNull() 只在读取整数或其他原始类型后才有用,而返回类型不能直接表示 null。 - Thomas W

4

你可以使用resultSet和具有Number类型的列名称来调用这个方法。它将返回整数值或null。对于数据库中的空值,不会返回零。

private Integer getIntWithNullCheck(ResultSet rset, String columnName) {
    try {
        Integer value = rset.getInt(columnName);
        return rset.wasNull() ? null : value;
    } catch (Exception e) {
        return null;
    }
}

你能否详细说明一下这是如何解决问题的? - Sterling Archer
您可以使用具有数字类型的resultSet和列名调用此方法。它将返回整数值或null。对于数据库中的空值,不会返回零。 - amine kriaa
太好了!将其编辑到您的答案中(并在编辑后删除评论),您就有一个很好的答案 :) - Sterling Archer

1

如果你想要一个替代 ResultSet.wasNull() 的方法,你可以使用 getObject() 并将其转换为正确的类型。

Long val = (Long)rs.getObject(pos++);

你也可以使用 setObject()Statement 中设置空值。

pstmt.setObject(pos++, null);

1

为了方便起见,您可以创建一个包装类,围绕ResultSet进行包装,当ResultSet通常不返回值时,它将返回null值。

public final class ResultSetWrapper {

    private final ResultSet rs;

    public ResultSetWrapper(ResultSet rs) {
        this.rs = rs;
    }

    public ResultSet getResultSet() {
        return rs;
    }

    public Boolean getBoolean(String label) throws SQLException {
        final boolean b = rs.getBoolean(label);
        if (rs.wasNull()) {
            return null;
        }
        return b;
    }

    public Byte getByte(String label) throws SQLException {
        final byte b = rs.getByte(label);
        if (rs.wasNull()) {
            return null;
        }
        return b;
    }

    // ...

}

1

如果有人在使用 Kotlin 进行编程时遇到此问题(就像我一样),BalusC 提供的答案很有效。只需注意 ShortFloatResultSet 中被实例化为 IntegerDouble(分别),因此在调用 getObject() 后,我们应该将它们强制转换为正确的类型。在我的情况下,最终代码如下:

when {
    propKClass.isSubclassOf(Int::class) -> rs.getObject(colName) as Int? 
    propKClass.isSubclassOf(Short::class) -> (rs.getObject(colName) as Int?)?.toShort()
    propKClass.isSubclassOf(Long::class) -> rs.getObject(colName) as Long?
    propKClass.isSubclassOf(Boolean::class) -> rs.getObject(colName) as Boolean?
    propKClass.isSubclassOf(Double::class) -> rs.getObject(colName) as Double?
    propKClass.isSubclassOf(Float::class) -> (rs.getObject(colName) as Double?)?.toFloat()
    else -> rs.getString(colName)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接