Het is lang geleden dat ik deze vraag heb gepost en ik wil een antwoord posten dat het exacte scenario beschrijft dat tot deze lastige NullPointerException
heeft geleid. .
Ik denk dat dit toekomstige lezers die zo'n verwarrende uitzondering tegenkomen, kan helpen om buiten de gebaande paden te denken, aangezien ik bijna alle reden had om te vermoeden dat dit een mysql-connectorfout was, ook al was het dat uiteindelijk niet.
Bij het onderzoeken van deze uitzondering was ik er zeker van dat mijn toepassing onmogelijk de DB-verbinding kon sluiten terwijl ik probeerde gegevens ervan te lezen, aangezien mijn DB-verbindingen niet over threads worden gedeeld, en als dezelfde thread de verbinding sloot en vervolgens probeerde toegang te krijgen het, had een andere uitzondering moeten worden gegenereerd (sommige SQLException
). Dat was de belangrijkste reden dat ik een bug in de mysql-connector vermoedde.
Het bleek dat er twee threads waren die toegang hadden tot dezelfde verbinding ten slotte. De reden waarom dit moeilijk te achterhalen was, was dat een van deze threads een thread was van een garbage collector .
Teruggaand naar de code die ik heb gepost:
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();
Het probleem ligt in de sectie "voer wat verwerking uit waarbij andere records uit de DB worden geladen met dezelfde verbinding", die ik helaas niet heb opgenomen in mijn oorspronkelijke vraag, omdat ik niet dacht dat het probleem daar was.
Als we op dat gedeelte inzoomen, hebben we:
if (sc != null) {
...
someMethod (conn);
...
}
En someMethod
ziet er zo uit:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
ziet er zo uit (natuurlijk vereenvoudig ik het hier):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
kan in sommige scenario's zijn eigen DB-verbinding maken, maar kan in andere scenario's een bestaande verbinding accepteren, zoals degene die we hier hebben.
Zoals je kunt zien, bevat die sectie een aanroep naar someMethod
die de open verbinding als argument accepteert. someMethod
geeft de verbinding door aan een lokale instantie van SomeOtherClass
. SomeOtherClass
had een finalize
methode die de verbinding verbreekt.
Nu, na someMethod
retourneert, instance
komt in aanmerking voor het ophalen van huisvuil. Wanneer het afval wordt verzameld, wordt het finalize
methode wordt aangeroepen door de Garbage Collector-thread, die de verbinding sluit.
Nu gaan we terug naar de for-lus, die doorgaat met het uitvoeren van SELECT-instructies met dezelfde verbinding die op elk moment kan worden gesloten door de thread van de vuilnisophaaldienst.
Als de garbagecollector-thread de verbinding sluit terwijl de toepassingsthread zich in het midden van een of andere mysql-connectormethode bevindt die afhankelijk is van de verbinding die moet worden geopend, wordt een NullPointerException
kan voorkomen.
De finalize
verwijderen methode heeft het probleem opgelost.
We overschrijven niet vaak de finalize
methode in onze lessen, waardoor het erg moeilijk was om de bug te lokaliseren.