Đã lâu rồi tôi không đăng câu hỏi này và tôi muốn đăng câu trả lời mô tả chính xác trường hợp dẫn đến NullPointerException
phức tạp này .
Tôi nghĩ rằng điều này có thể giúp những độc giả trong tương lai gặp phải trường hợp ngoại lệ khó hiểu như vậy suy nghĩ kỹ hơn, vì tôi có hầu hết mọi lý do để nghi ngờ đây là lỗi trình kết nối mysql, mặc dù rốt cuộc thì không phải vậy.
Trong khi điều tra ngoại lệ này, tôi chắc chắn rằng ứng dụng của mình không thể đóng kết nối DB trong khi cố đọc dữ liệu từ nó, vì các kết nối DB của tôi không được chia sẻ trên các chuỗi và nếu cùng một chuỗi đã đóng kết nối và sau đó cố gắng truy cập nó, một ngoại lệ khác nên đã được ném ra (một số SQLException
). Đó là lý do chính khiến tôi nghi ngờ có lỗi trình kết nối mysql.
Hóa ra là có hai luồng truy cập vào cùng một kết nối sau tất cả. Khó tìm ra lý do khiến điều này xảy ra là một trong những chuỗi này là một chuỗi thu gom rác .
Quay lại mã tôi đã đăng:
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();
Vấn đề nằm ở phần "thực hiện một số xử lý liên quan đến việc tải các bản ghi khác từ DB bằng cách sử dụng cùng một kết nối", thật không may, tôi đã không đưa vào câu hỏi ban đầu của mình, vì tôi không nghĩ rằng vấn đề là ở đó.
Phóng to phần đó, chúng ta có:
if (sc != null) {
...
someMethod (conn);
...
}
Và someMethod
trông như thế này:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
trông như thế này (tất nhiên tôi đang đơn giản hóa ở đây):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
có thể tạo kết nối DB của riêng nó trong một số trường hợp, nhưng có thể chấp nhận kết nối hiện có trong các trường hợp khác, như kết nối chúng tôi có ở đây.
Như bạn có thể thấy, Phần đó chứa lệnh gọi đến someMethod
chấp nhận kết nối mở làm đối số. someMethod
chuyển kết nối đến một phiên bản cục bộ của SomeOtherClass
. SomeOtherClass
có một finalize
phương thức đóng kết nối.
Bây giờ, sau someMethod
trả về, instance
trở nên đủ điều kiện để thu gom rác. Khi nó được thu gom rác, finalize
phương thức được gọi bởi luồng thu gom rác, luồng này sẽ đóng kết nối.
Bây giờ chúng ta quay lại vòng lặp for, tiếp tục thực hiện các câu lệnh SELECT bằng cách sử dụng cùng một kết nối có thể bị đóng bởi luồng thu gom rác bất kỳ lúc nào.
Nếu chuỗi bộ thu gom rác tình cờ đóng kết nối trong khi chuỗi ứng dụng đang ở giữa một số phương thức trình kết nối mysql dựa vào kết nối để mở, một NullPointerException
có thể xảy ra.
Xóa finalize
phương pháp đã giải quyết vấn đề.
Chúng tôi không thường ghi đè finalize
trong các lớp của chúng tôi, điều này khiến việc xác định lỗi rất khó khăn.