Už je to dlouho, co jsem tuto otázku zveřejnil, a chci zveřejnit odpověď, která popisuje přesný scénář, který vedl k této záludné NullPointerException
.
Myslím, že to může pomoci budoucím čtenářům, kteří se setkají s tak matoucí výjimkou, přemýšlet mimo rámec, protože jsem měl téměř všechny důvody k podezření, že se jedná o chybu mysql konektoru, i když tomu tak nakonec nebylo.
Při zkoumání této výjimky jsem si byl jistý, že moje aplikace nemůže zavírat připojení k databázi, když se z ní pokouší číst data, protože moje připojení DB nejsou sdílena mezi vlákny a pokud stejné vlákno připojení uzavřelo a poté se pokusilo o přístup měla být vyvolána jiná výjimka (některá SQLException
). To byl hlavní důvod, proč jsem měl podezření na chybu mysql konektoru.
Ukázalo se, že existovala dvě vlákna přistupující ke stejnému připojení po všem. Důvod, proč to bylo těžké zjistit, byl ten, že jedno z těchto vláken bylo vlákno pro sběrač odpadu .
Vracím se ke kódu, který jsem zveřejnil:
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();
Problém spočívá v sekci „proveďte nějaké zpracování, které zahrnuje načtení dalších záznamů z DB pomocí stejného připojení“, kterou jsem bohužel do své původní otázky nezahrnul, protože jsem si nemyslel, že tam problém je.
Když přiblížíme tuto sekci, máme:
if (sc != null) {
...
someMethod (conn);
...
}
A someMethod
vypadá takto:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
vypadá takto (samozřejmě zde zjednodušuji):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
může v některých scénářích vytvořit své vlastní připojení k databázi, ale v jiných scénářích, jako je ten, který máme zde, může přijmout existující připojení.
Jak vidíte, tato sekce obsahuje volání someMethod
který akceptuje otevřené spojení jako argument. someMethod
předá připojení místní instanci SomeOtherClass
. SomeOtherClass
měl finalize
metoda, která uzavře spojení.
Nyní, po someMethod
vrátí, instance
se stává způsobilým pro svoz odpadu. Když je odpad shromážděn, jeho finalize
metoda je volána vláknem garbage collector, čímž se spojení uzavře.
Nyní se vrátíme ke smyčce for, která pokračuje ve vykonávání příkazů SELECT pomocí stejného připojení, které může být kdykoli uzavřeno garbage collectorem.
Pokud vlákno garbage collector zavře připojení, zatímco aplikační vlákno je uprostřed nějaké metody mysql konektoru, která spoléhá na to, že připojení je otevřené, NullPointerException
může dojít.
Odstranění finalize
metoda problém vyřešila.
finalize
často nepřepisujeme metoda v našich třídách, což velmi ztěžovalo lokalizaci chyby.