sql >> Base de Datos >  >> RDS >> Mysql

NullPointerException ocasional en ResultSetImpl.checkColumnBounds o ResultSetImpl.getStringInternal

Ha pasado mucho tiempo desde que publiqué esta pregunta y quiero publicar una respuesta que describa el escenario exacto que condujo a esta engañosa NullPointerException .

Creo que esto puede ayudar a los futuros lectores que encuentren una excepción tan confusa a pensar fuera de la caja, ya que tenía casi todas las razones para sospechar que se trataba de un error del conector mysql, aunque después de todo no lo era.

Mientras investigaba esta excepción, estaba seguro de que mi aplicación no podía estar cerrando la conexión de la base de datos mientras intentaba leer datos de ella, ya que mis conexiones de la base de datos no se comparten entre subprocesos, y si el mismo subproceso cerraba la conexión y luego intentaba acceder debería haberse lanzado una excepción diferente (algunas SQLException ). Esa fue la razón principal por la que sospeché un error en el conector mysql.

Resultó que había dos subprocesos accediendo a la misma conexión después de todo. La razón por la que esto fue difícil de entender fue que uno de estos subprocesos era un recolector de basura .

Volviendo al código que publiqué:

Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
    SomeClass sc = null;
    PreparedStatement
        stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
    stmt.setString (1, "someID");
    ResultSet res = stmt.executeQuery ();
    if (res.next ()) {
        sc = new SomeClass ();
        sc.setA (res.getString (1));
        sc.setB (res.getString (2));
        sc.setC (res.getString (3));
        sc.setD (res.getString (4));
        sc.setE (res.getString (5));
        sc.setF (res.getInt (6));
        sc.setG (res.getString (7));
        sc.setH (res.getByte (8)); // the exception is thrown here
    }
    stmt.close ();
    conn.commit ();
    if (sc != null) {
        // do some processing that involves loading other records from the
        // DB using the same connection
    }
}
conn.close();

El problema radica en la sección "hacer algún procesamiento que involucre cargar otros registros de la base de datos usando la misma conexión", que, desafortunadamente, no incluí en mi pregunta original, ya que no pensé que el problema estaba ahí.

Haciendo zoom en esa sección, tenemos:

if (sc != null) {
    ...
    someMethod (conn);
    ...
}

Y someMethod se parece a esto:

public void someMethod (Connection conn) 
{
    ...
    SomeOtherClass instance = new SomeOtherClass (conn);
    ...
}

SomeOtherClass se ve así (por supuesto que estoy simplificando aquí):

public class SomeOtherClass
{
    Connection conn;

    public SomeOtherClass (Connection conn) 
    {
        this.conn = conn;
    }

    protected void finalize() throws Throwable
    { 
        if (this.conn != null)
            conn.close();
    }

}

SomeOtherClass puede crear su propia conexión de base de datos en algunos escenarios, pero puede aceptar una conexión existente en otros escenarios, como el que tenemos aquí.

Como puede ver, esa sección contiene una llamada a someMethod que acepta la conexión abierta como argumento. someMethod pasa la conexión a una instancia local de SomeOtherClass . SomeOtherClass tenía un finalize método que cierra la conexión.

Ahora, después de someMethod devuelve, instance pasa a ser elegible para la recolección de basura. Cuando se recolecta basura, es finalize El subproceso del recolector de elementos no utilizados llama al método y cierra la conexión.

Ahora volvemos al ciclo for, que continúa ejecutando sentencias SELECT usando la misma conexión que puede ser cerrada en cualquier momento por el hilo del recolector de basura.

Si el subproceso del recolector de basura cierra la conexión mientras el subproceso de la aplicación está en medio de algún método de conector mysql que depende de que la conexión esté abierta, una NullPointerException puede ocurrir.

Eliminando el finalize El método resolvió el problema.

No solemos anular el finalize en nuestras clases, lo que hizo muy difícil localizar el error.