Mysql
 sql >> Base de données >  >> RDS >> Mysql

NullPointerException occasionnelle dans ResultSetImpl.checkColumnBounds ou ResultSetImpl.getStringInternal

Cela fait longtemps que j'ai posté cette question, et je veux poster une réponse qui décrit le scénario exact qui a conduit à cette délicate NullPointerException .

Je pense que cela peut aider les futurs lecteurs qui rencontrent une exception aussi déroutante à sortir des sentiers battus, car j'avais presque toutes les raisons de soupçonner qu'il s'agissait d'un bogue du connecteur mysql, même si ce n'était pas le cas après tout.

En enquêtant sur cette exception, j'étais sûr que mon application ne pouvait pas fermer la connexion à la base de données tout en essayant d'en lire les données, car mes connexions à la base de données ne sont pas partagées entre les threads, et si le même thread fermait la connexion puis essayait d'accéder cela, une exception différente aurait dû être lancée (certaines SQLException ). C'est la principale raison pour laquelle j'ai suspecté un bug du connecteur mysql.

Il s'est avéré que il y avait deux threads accédant à la même connexion après tout. La raison pour laquelle c'était difficile à comprendre était que l'un de ces threads était un thread de récupération de place .

Revenons au code que j'ai posté :

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

Le problème réside dans la section "faire un traitement qui implique de charger d'autres enregistrements de la base de données en utilisant la même connexion", que, malheureusement, je n'ai pas inclus dans ma question d'origine, car je ne pensais pas que le problème était là.

En zoomant sur cette section, nous avons :

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

Et someMethod ressemble à ceci :

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

SomeOtherClass ressemble à ceci (bien sûr, je simplifie ici):

public class SomeOtherClass
{
    Connection conn;

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

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

}

SomeOtherClass peut créer sa propre connexion DB dans certains scénarios, mais peut accepter une connexion existante dans d'autres scénarios, comme celui que nous avons ici.

Comme vous pouvez le voir, cette section contient un appel à someMethod qui accepte la connexion ouverte comme argument. someMethod passe la connexion à une instance locale de SomeOtherClass . SomeOtherClass eu une finalize méthode qui ferme la connexion.

Maintenant, après someMethod renvoie, instance devient admissible à la collecte des ordures. Lorsqu'il est ramassé, il est finalize est appelée par le thread du ramasse-miettes, qui ferme la connexion.

Nous revenons maintenant à la boucle for, qui continue d'exécuter des instructions SELECT en utilisant la même connexion qui peut être fermée à tout moment par le thread du ramasse-miettes.

Si le thread du ramasse-miettes ferme la connexion alors que le thread de l'application est au milieu d'une méthode de connecteur mysql qui repose sur l'ouverture de la connexion, une NullPointerException peut se produire.

Suppression de la finalize méthode a résolu le problème.

Nous ne remplaçons pas souvent le finalize méthode dans nos classes, ce qui a rendu très difficile la localisation du bogue.