Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

NullPointerException ocasional em ResultSetImpl.checkColumnBounds ou ResultSetImpl.getStringInternal


Já faz muito tempo desde que postei essa pergunta e quero postar uma resposta que descreva o cenário exato que levou a essa NullPointerException complicada .

Eu acho que isso pode ajudar futuros leitores que encontrarem uma exceção tão confusa a pensar fora da caixa, já que eu tinha quase todos os motivos para suspeitar que isso era um bug do conector mysql, mesmo que não fosse afinal.

Ao investigar essa exceção, tive certeza de que meu aplicativo não pode fechar a conexão de banco de dados ao tentar ler dados dela, pois minhas conexões de banco de dados não são compartilhadas entre threads e se o mesmo thread fechasse a conexão e tentasse acessar isso, uma exceção diferente deveria ter sido lançada (algumas SQLException ). Essa foi a principal razão pela qual suspeitei de um bug no conector mysql.

Descobriu-se que havia dois encadeamentos acessando a mesma conexão afinal. A razão pela qual isso foi difícil de descobrir foi que um desses threads era um thread do coletor de lixo .

Voltando ao código que postei:
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();

O problema está na seção "fazer algum processamento que envolva carregar outros registros do banco de dados usando a mesma conexão", que, infelizmente, não incluí na minha pergunta original, pois não achei que o problema estivesse lá.

Ampliando essa seção, temos:
if (sc != null) {
    ...
    someMethod (conn);
    ...
}

E someMethod se parece com isso:
public void someMethod (Connection conn) 
{
    ...
    SomeOtherClass instance = new SomeOtherClass (conn);
    ...
}

SomeOtherClass se parece com isso (é claro que estou simplificando aqui):
public class SomeOtherClass
{
    Connection conn;

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

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

}

SomeOtherClass pode criar sua própria conexão de banco de dados em alguns cenários, mas pode aceitar uma conexão existente em outros cenários, como o que temos aqui.

Como você pode ver, essa seção contém uma chamada para someMethod que aceita a conexão aberta como argumento. someMethod passa a conexão para uma instância local de SomeOtherClass . SomeOtherClass teve um finalize método que fecha a conexão.

Agora, depois de someMethod retorna, instance torna-se elegível para a coleta de lixo. Quando é coletado como lixo, seu finalize é chamado pelo thread do coletor de lixo, que fecha a conexão.

Agora voltamos ao loop for, que continua a executar instruções SELECT usando a mesma conexão que pode ser fechada a qualquer momento pela thread do coletor de lixo.

Se o thread do coletor de lixo fechar a conexão enquanto o thread do aplicativo estiver no meio de algum método de conector mysql que depende da conexão ser aberta, um NullPointerException pode ocorrer.

Removendo o finalize método resolveu o problema.

Não costumamos substituir o finalize em nossas classes, o que dificultou muito a localização do bug.