Sudah lama sejak saya memposting pertanyaan ini, dan saya ingin memposting jawaban yang menjelaskan skenario persis yang menyebabkan NullPointerException
yang rumit ini .
Saya pikir ini dapat membantu pembaca masa depan yang menemukan pengecualian yang membingungkan untuk berpikir di luar kebiasaan, karena saya memiliki hampir semua alasan untuk mencurigai ini adalah bug konektor mysql, meskipun sebenarnya tidak.
Saat menyelidiki pengecualian ini, saya yakin bahwa aplikasi saya tidak mungkin menutup koneksi DB saat mencoba membaca data darinya, karena koneksi DB saya tidak dibagikan di seluruh utas, dan jika utas yang sama menutup koneksi dan kemudian mencoba mengakses itu, pengecualian yang berbeda seharusnya dilemparkan (beberapa SQLException
). Itulah alasan utama saya mencurigai adanya bug konektor mysql.
Ternyata ada dua thread mengakses koneksi yang sama Lagipula. Alasan mengapa hal ini sulit diketahui adalah karena salah satu utas ini adalah utas pengumpul sampah .
Kembali ke kode yang saya posting:
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();
Masalahnya terletak pada bagian "lakukan beberapa pemrosesan yang melibatkan pemuatan catatan lain dari DB menggunakan koneksi yang sama", yang, sayangnya, tidak saya sertakan dalam pertanyaan awal saya, karena saya pikir masalahnya tidak ada di sana.
Memperbesar bagian itu, kami memiliki:
if (sc != null) {
...
someMethod (conn);
...
}
Dan someMethod
terlihat seperti ini:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
terlihat seperti ini (tentu saja saya sederhanakan di sini):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
dapat membuat koneksi DB sendiri dalam beberapa skenario, tetapi dapat menerima koneksi yang ada dalam skenario lain, seperti yang kita miliki di sini.
Seperti yang Anda lihat, Bagian itu berisi panggilan ke someMethod
yang menerima koneksi terbuka sebagai argumen. someMethod
meneruskan koneksi ke instance lokal SomeOtherClass
. SomeOtherClass
telah finalize
metode yang menutup koneksi.
Sekarang, setelah someMethod
kembali, instance
memenuhi syarat untuk pengumpulan sampah. Saat sampah dikumpulkan, finalize
metode ini dipanggil oleh utas pengumpul sampah, yang menutup koneksi.
Sekarang kita kembali ke loop for, yang terus mengeksekusi pernyataan SELECT menggunakan koneksi yang sama yang dapat ditutup kapan saja oleh thread pengumpul sampah.
Jika utas pengumpul sampah menutup koneksi saat utas aplikasi berada di tengah beberapa metode konektor mysql yang bergantung pada koneksi untuk dibuka, NullPointerException
mungkin terjadi.
Menghapus finalize
metode memecahkan masalah.
Kami tidak sering mengganti finalize
metode di kelas kami, yang membuatnya sangat sulit untuk menemukan bug.