Verificación de un valor int nulo desde un ResultSet de Java

277

En Java, estoy tratando de probar un valor nulo, desde un ResultSet, donde la columna se está convirtiendo en un tipo int primitivo .

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

Desde el fragmento de código anterior, ¿hay una mejor manera de hacer esto, y supongo que la segunda prueba wasNull () es redundante?

Edúcanos y gracias

ian_scho
fuente
14
Encontré esta pregunta porque tengo una columna anulable en una base de datos y está representada por un número entero en Java. Pensaría que tener una columna numérica anulable en una base de datos sería lo suficientemente común como para que la API ResultSet lo acomode un poco más elegantemente.
spaaarky21
No estoy publicando esto como una respuesta porque es tangencial y está lejos de ser universal: mi solución habitual es poner IF(colName = NULL, 0, colName) AS colNameen la SELECTdeclaración (preferiblemente en un proceso almacenado). Filosóficamente, esto se reduce a si la base de datos debe ajustarse a la aplicación, o viceversa. Dado que SQL maneja NULLs fácilmente y muchos consumidores de SQL no lo hacen (es decir java.sql.ResultSet), opto por manejarlo en la base de datos cuando sea posible. (Esto, por supuesto, supone que conceptualmente NULL y zero son equivalentes para sus propósitos.)
s.co.tt

Respuestas:

368

El valor predeterminado para ResultSet.getIntcuando el valor del campo es NULLdevolver 0, que también es el valor predeterminado para su iValdeclaración. En cuyo caso su prueba es completamente redundante.

Si realmente desea hacer algo diferente si el valor del campo es NULL, sugiero:

int iVal = 0;
ResultSet rs = magicallyAppearingStmt.executeQuery(query);
if (rs.next()) {
    iVal = rs.getInt("ID_PARENT");
    if (rs.wasNull()) {
        // handle NULL field value
    }
}

(Editado como @martin comenta a continuación; el código OP como está escrito no se compilaría porque iValno está inicializado)

Ricardo
fuente
2
Acabo de encontrar la misma declaración en los documentos. Vale la pena un hilo separado en SO imho. ( java.sun.com/j2se/1.4.2/docs/api/java/sql/… )
Romano
66
@Roman: vea el javadoc para getInt en ResultSet: "Devuelve: el valor de la columna; si el valor es SQL NULL, el valor devuelto es 0"
Cowan
150
La verdad es, de hecho, ridícula. getInt()debería ser el getInteger()que devuelve un Integeres decir nullsi el valor de DB es null. Los desarrolladores realmente lo estropearon.
ryvantage
77
@sactiw siguiendo esta lógica, todo en Java debería haberse desarrollado para evitar NPE, que no es el caso. Evitar NPE es responsabilidad de los desarrolladores de la aplicación, no del lenguaje interno de las API.
Mateus Viccari
10
OMG ¿Por qué lo hizo, simplemente no devuelve NULL, 0y NULLson dos cosas muy diferentes
deFreitas
84

Otra solución:

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

Creo que es redundante. rs.getObject("ID_PARENT")debería devolver un Integerobjeto o null, si el valor de la columna realmente fuera NULL. Por lo tanto, incluso debería ser posible hacer algo como:

if (rs.next()) {
  Integer idParent = (Integer) rs.getObject("ID_PARENT");
  if (idParent != null) {
    iVal = idParent; // works for Java 1.5+
  } else {
    // handle this case
  }      
}
Andreas Dolk
fuente
55
Hm, al menos en mi caso, el problema con esto es que las llamadas getObjectno necesariamente devuelven un número entero, debido a la naturaleza del tipo de columna en el oráculo db que estoy usando ("Número").
Matt Mc
El mismo problema de Matt ... Con MySQL y Types.BIGINT(que debería asignarse a a Long) el getObject()método devuelve 0 en lugar de nulo.
xonya
2
También podría hacerlors.getObject("ID_PARENT", Integer.class)
Arlo
26

Simplemente verifique si el campo está nullutilizando o no ResultSet#getObject(). Sustituya -1con el valor de mayúsculas y minúsculas que desee.

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

O bien, si puede garantizar que utiliza el tipo de columna de base de datos correcto para que ResultSet#getObject()realmente devuelva un Integer(y, por lo tanto Long, no , Shorto Byte), también puede simplemente escribirlo en un Integer.

Integer foo = (Integer) resultSet.getObject("foo");
BalusC
fuente
1
No, pero construirá un objeto entero innecesario en el caso no nulo. (Y, por cierto, la mayoría de los controladores JDBC no alcanzan la base de datos durante ninguna llamada al método ResultSet ... en general, no obtiene el ResultSet hasta que todos los datos se hayan transferido).
EricS
Depende del tamaño de búsqueda. En la mayoría de los controladores, el valor predeterminado es 10 filas y una vez que se haya recuperado la recuperación, se procesarán, pero la siguiente recuperación no se recuperará hasta que el procesamiento haya finalizado.
Kristo Aun
Me sorprende que su respuesta no sea la mejor respuesta :-) Voto porque es la respuesta correcta que evita el método inseguro wasNull (). Para mí, es una razón adicional para dejar de usar Java ;-) y continuar usando VB.Net donde RecordSet ha resuelto este problema fácil desde hace más de 10 años.
schlebe
11

AFAIK simplemente puedes usar

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

incluso si es NULL.

Peter Tillemans
fuente
5

Solo una actualización con Java Generics.

Podría crear un método de utilidad para recuperar un valor opcional de cualquier tipo de Java de un ResultSet dado, previamente emitido.

Desafortunadamente, getObject (columnName, Class) no devuelve nulo, pero el valor predeterminado para un tipo de Java dado, por lo que se requieren 2 llamadas

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;
}

En este ejemplo, su código podría verse a continuación:

final Integer columnValue = getOptionalValue(rs, Integer.class);
if (columnValue == null) {
    //null handling
} else {
    //use int value of columnValue with autoboxing
}

Feliz de recibir comentarios

luchoct
fuente
2

Puede llamar a este método usando resultSet y el nombre de la columna que tiene el tipo Number. Devolverá el valor entero o nulo. No habrá ceros devueltos por valor vacío en la base de datos

private Integer getIntWithNullCheck(ResultSet rset, String columnName) {
    try {
        Integer value = rset.getInt(columnName);
        return rset.wasNull() ? null : value;
    } catch (Exception e) {
        return null;
    }
}
amine kriaa
fuente
¿Puede por favor entrar en más detalles sobre cómo esto resuelve la pregunta?
Sterling Archer
Puede llamar a este método usando resultSet y el nombre de la columna que tiene el tipo Number. Devolverá el valor entero o nulo. No habrá ceros devueltos para el valor vacío en la base de datos.
amine kriaa
¡Excelente! Edítelo en su respuesta (y elimine el comentario después de la edición) y tendrá una buena respuesta :)
Sterling Archer
1

Con java 8 puedes hacer esto:

Long nVal = Optional.ofNullable(resultSet.getBigDecimal("col_name"))
                    .map(BigDecimal::longValue).orElse(null));

En ese caso, se asegura de que nVal será nulo (y no cero) si el valor de SQL es NULL

Jorge
fuente
2
pero no se aplica aresultSet.getInt("col_name")
rapto
1
Con la fuente de datos MSSQL, esto no parece funcionar. Debe tener la verificación adicional deif (rs.wasNull())
alltej
1

Para mayor comodidad, puede crear una clase de contenedor alrededor de ResultSet que devuelva valores nulos cuando ResultSetnormalmente no lo haría.

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;
    }

    // ...

}
Jacob Crofts
fuente
-6

Otra buena manera de verificar, si tiene control del SQL, es agregar un valor predeterminado en la consulta para su columna int. Luego solo verifique ese valor.

por ejemplo, para una base de datos Oracle, use NVL

SELECT NVL(ID_PARENT, -999) FROM TABLE_NAME;

Entonces revisa

if (rs.getInt('ID_PARENT') != -999)
{
}

Por supuesto, esto también se da por supuesto que hay un valor que normalmente no se encontraría en la columna.

Chris
fuente
12
He rechazado esta respuesta, ya que es muy probable que cause problemas a muchas personas. Una columna int si se define como nulable tiene un conjunto de valores que consta de números positivos, cero, números negativos y NULL. En cualquier momento, uno simplemente puede insertar una fila de datos válida que contenga este número mágico y todas las cosas repentinas irán mal. Básicamente es la implementación del anti patrón de números mágicos. No hagas esto.
Matthias Hryniszak