Minęło dużo czasu, odkąd opublikowałem to pytanie i chcę opublikować odpowiedź, która opisuje dokładny scenariusz, który doprowadził do tego trudnego NullPointerException
.
Myślę, że może to pomóc przyszłym czytelnikom, którzy napotkają tak mylący wyjątek, myśleć nieszablonowo, ponieważ miałem prawie wszelkie powody, by podejrzewać, że jest to błąd złącza mysql, mimo że tak nie było.
Podczas badania tego wyjątku byłem pewien, że moja aplikacja nie może zamykać połączenia z bazą danych podczas próby odczytu z niej danych, ponieważ moje połączenia z bazą danych nie są współdzielone przez wątki, a jeśli ten sam wątek zamknął połączenie, a następnie próbował uzyskać dostęp powinien zostać zgłoszony inny wyjątek (niektóre SQLException
). To był główny powód, dla którego podejrzewałem błąd złącza mysql.
Okazało się, że dwa wątki miały dostęp do tego samego połączenia w końcu. Powodem, dla którego trudno było to rozgryźć, było to, że jeden z tych wątków był wątkiem odśmiecacza .
Wracając do kodu, który opublikowałem:
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();
Problem tkwi w sekcji „zrób trochę przetwarzania, która polega na załadowaniu innych rekordów z bazy danych przy użyciu tego samego połączenia”, której niestety nie uwzględniłem w moim pierwotnym pytaniu, ponieważ nie sądziłem, że jest w tym problem.
Powiększając tę sekcję, mamy:
if (sc != null) {
...
someMethod (conn);
...
}
I someMethod
wygląda tak:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
wygląda tak (oczywiście upraszczam):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
może utworzyć własne połączenie z bazą danych w niektórych scenariuszach, ale może zaakceptować istniejące połączenie w innych scenariuszach, takie jak to, które mamy tutaj.
Jak widać, sekcja That zawiera wywołanie someMethod
który akceptuje otwarte połączenie jako argument. someMethod
przekazuje połączenie do lokalnego wystąpienia SomeOtherClass
. SomeOtherClass
miał finalize
metoda zamykająca połączenie.
Teraz, po someMethod
zwraca, instance
kwalifikuje się do zbierania śmieci. Kiedy jest zbierany śmieci, jego finalize
Metoda jest wywoływana przez wątek garbage collector, który zamyka połączenie.
Teraz wracamy do pętli for, która kontynuuje wykonywanie instrukcji SELECT przy użyciu tego samego połączenia, które może zostać zamknięte w dowolnym momencie przez wątek garbage collector.
Jeśli wątek garbage collector zamyka połączenie, gdy wątek aplikacji znajduje się w środku jakiejś metody konektora mysql, która opiera się na otwartym połączeniu, NullPointerException
może wystąpić.
Usuwanie finalize
metoda rozwiązała problem.
Nieczęsto nadpisujemy finalize
w naszych klasach, co bardzo utrudniło zlokalizowanie błędu.