Mysql
 sql >> Database >  >> RDS >> Mysql

Occasionale NullPointerException in ResultSetImpl.checkColumnBounds o ResultSetImpl.getStringInternal

È passato molto tempo da quando ho pubblicato questa domanda e voglio pubblicare una risposta che descriva lo scenario esatto che ha portato a questa difficile NullPointerException .

Penso che questo potrebbe aiutare i futuri lettori che incontrano un'eccezione così confusa a pensare fuori dagli schemi, dal momento che avevo quasi tutte le ragioni per sospettare che si trattasse di un bug del connettore MySQL, anche se dopo tutto non lo era.

Durante l'analisi di questa eccezione ero sicuro che la mia applicazione non potesse chiudere la connessione DB durante il tentativo di leggere i dati da essa, poiché le mie connessioni DB non sono condivise tra i thread e se lo stesso thread ha chiuso la connessione e quindi ha tentato di accedere esso, avrebbe dovuto essere generata un'eccezione diversa (alcuni SQLException ). Questo era il motivo principale per cui sospettavo un bug del connettore MySQL.

Si è scoperto che c'erano due thread che accedevano alla stessa connessione Dopotutto. Il motivo per cui era difficile da capire era che uno di questi thread era un thread di Garbage Collector .

Tornando al codice che ho postato:

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();

Il problema sta nella sezione "eseguire alcune elaborazioni che comportano il caricamento di altri record dal DB utilizzando la stessa connessione", che, sfortunatamente, non ho incluso nella mia domanda originale, poiché non pensavo che il problema fosse lì.

Zoomando in quella sezione, abbiamo:

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

E someMethod assomiglia a questo:

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

SomeOtherClass si presenta così (ovviamente sto semplificando qui):

public class SomeOtherClass
{
    Connection conn;

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

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

}

SomeOtherClass può creare la propria connessione DB in alcuni scenari, ma può accettare una connessione esistente in altri scenari, come quello che abbiamo qui.

Come puoi vedere, quella sezione contiene una chiamata a someMethod che accetta la connessione aperta come argomento. someMethod passa la connessione a un'istanza locale di SomeOtherClass . SomeOtherClass aveva un finalize metodo che chiude la connessione.

Ora, dopo someMethod restituisce, instance diventa idoneo alla raccolta dei rifiuti. Quando viene effettuato il Garbage Collection, il suo finalize viene chiamato dal thread del Garbage Collector, che chiude la connessione.

Ora torniamo al ciclo for, che continua a eseguire le istruzioni SELECT utilizzando la stessa connessione che può essere chiusa in qualsiasi momento dal thread del Garbage Collector.

Se il thread del Garbage Collector chiude la connessione mentre il thread dell'applicazione si trova nel mezzo di un metodo del connettore mysql che si basa sulla connessione per essere aperta, un NullPointerException può verificarsi.

Rimozione di finalize il metodo ha risolto il problema.

Non sostituiamo spesso finalize metodo nelle nostre classi, il che ha reso molto difficile individuare il bug.