Truy cập cơ sở dữ liệu đồng thời
Cùng một bài viết trên blog của tôi (tôi thích định dạng hơn)
Tôi đã viết một bài viết nhỏ mô tả cách làm thế nào để truy cập vào chủ đề cơ sở dữ liệu Android của bạn an toàn.
Giả sử bạn có SQLiteOpenHelper của riêng bạn .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Bây giờ bạn muốn ghi dữ liệu vào cơ sở dữ liệu trong các luồng riêng biệt.
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
Bạn sẽ nhận được thông báo sau trong logcat của bạn và một trong những thay đổi của bạn sẽ không được viết.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Điều này xảy ra bởi vì mỗi khi bạn tạo đối tượng SQLiteOpenHelper mới, bạn thực sự tạo kết nối cơ sở dữ liệu mới. Nếu bạn cố gắng ghi vào cơ sở dữ liệu từ các kết nối riêng biệt thực tế cùng một lúc, người ta sẽ thất bại. (từ câu trả lời ở trên)
Để sử dụng cơ sở dữ liệu với nhiều luồng, chúng tôi cần đảm bảo rằng chúng tôi đang sử dụng một kết nối cơ sở dữ liệu.
Chúng ta hãy tạo Trình quản lý cơ sở dữ liệu lớp đơn sẽ giữ và trả về một đối tượng SQLiteOpenHelper duy nhất .
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
return instance;
}
public SQLiteDatabase getDatabase() {
return new mDatabaseHelper.getWritableDatabase();
}
}
Mã cập nhật ghi dữ liệu vào cơ sở dữ liệu trong các luồng riêng biệt sẽ trông như thế này.
// In your application class
DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
Điều này sẽ mang lại cho bạn một vụ tai nạn khác.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Vì chúng tôi chỉ sử dụng một kết nối cơ sở dữ liệu, phương thức getDatabase () trả về cùng phiên bản của đối tượng SQLiteDatabase cho Thread1 và Thread2 . Điều gì đang xảy ra, Thread1 có thể đóng cơ sở dữ liệu, trong khi Thread2 vẫn đang sử dụng nó. Đó là lý do tại sao chúng tôi gặp sự cố IllegalStateException .
Chúng ta cần đảm bảo không ai đang sử dụng cơ sở dữ liệu và chỉ sau đó đóng nó. Một số người trên stackoveflow được khuyến nghị không bao giờ đóng SQLiteDatabase của bạn . Điều này sẽ dẫn đến thông điệp logcat sau.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Mẫu làm việc
public class DatabaseManager {
private int mOpenCounter;
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
mOpenCounter++;
if(mOpenCounter == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
mOpenCounter--;
if(mOpenCounter == 0) {
// Closing database
mDatabase.close();
}
}
}
Sử dụng nó như sau.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Mỗi khi bạn cần cơ sở dữ liệu, bạn nên gọi phương thức openDatabase () của lớp DatabaseManager . Bên trong phương thức này, chúng ta có một bộ đếm, cho biết số lần cơ sở dữ liệu được mở. Nếu nó bằng một, điều đó có nghĩa là chúng ta cần tạo kết nối cơ sở dữ liệu mới, nếu không, kết nối cơ sở dữ liệu đã được tạo.
Điều tương tự cũng xảy ra trong phương thức closeDatabase () . Mỗi khi chúng ta gọi phương thức này, bộ đếm bị giảm, bất cứ khi nào nó về 0, chúng ta sẽ đóng kết nối cơ sở dữ liệu.
Bây giờ bạn sẽ có thể sử dụng cơ sở dữ liệu của mình và chắc chắn rằng chuỗi đó an toàn.