การเข้าถึงฐานข้อมูลพร้อมกัน
บทความเดียวกันในบล็อกของฉัน (ฉันชอบการจัดรูปแบบเพิ่มเติม)
ฉันเขียนบทความเล็ก ๆ ซึ่งอธิบายวิธีการเข้าถึงเธรดฐานข้อมูล android ของคุณอย่างปลอดภัย
สมมติว่าคุณมีของคุณเองSQLiteOpenHelper
public class DatabaseHelper extends SQLiteOpenHelper { ... }
ตอนนี้คุณต้องการเขียนข้อมูลไปยังฐานข้อมูลในหัวข้อแยก
// 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();
คุณจะได้รับข้อความต่อไปนี้ใน logcat ของคุณและหนึ่งในการเปลี่ยนแปลงของคุณจะไม่ถูกเขียน
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
สิ่งนี้เกิดขึ้นเพราะทุกครั้งที่คุณสร้างวัตถุSQLiteOpenHelperใหม่คุณกำลังทำการเชื่อมต่อฐานข้อมูลใหม่ หากคุณพยายามที่จะเขียนไปยังฐานข้อมูลจากการเชื่อมต่อที่แตกต่างกันจริงในเวลาเดียวกันหนึ่งจะล้มเหลว (จากคำตอบด้านบน)
ในการใช้ฐานข้อมูลที่มีหลายเธรดเราต้องแน่ใจว่าเราใช้การเชื่อมต่อฐานข้อมูลเดียว
ลองสร้างตัวจัดการฐานข้อมูลแบบชั้นเดียวซึ่งจะเก็บและส่งคืนวัตถุSQLiteOpenHelperเดียว
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();
}
}
อัปเดตรหัสซึ่งเขียนข้อมูลไปยังฐานข้อมูลในกระทู้แยกจะมีลักษณะเช่นนี้
// 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();
นี่จะทำให้คุณพังอีก
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
เนื่องจากเรามีการใช้เพียงการเชื่อมต่อฐานข้อมูลหนึ่งวิธีgetDatabase ()กลับเช่นเดียวกันของSQLiteDatabaseวัตถุสำหรับThread1และThread2 เกิดอะไรขึ้นThread1อาจปิดฐานข้อมูลในขณะที่Thread2ยังคงใช้งานอยู่ นั่นเป็นสาเหตุที่เรามีIllegalStateExceptionขัดข้อง
เราต้องตรวจสอบให้แน่ใจว่าไม่มีใครใช้ฐานข้อมูลและปิดแล้วเท่านั้น บางคนใน stackoveflow แนะนำให้ไม่เคยใกล้ชิดของคุณSQLiteDatabase ซึ่งจะส่งผลให้ข้อความ logcat ดังต่อไปนี้
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
ตัวอย่างการทำงาน
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();
}
}
}
ใช้มันดังต่อไปนี้
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
ทุกครั้งที่คุณต้องการฐานข้อมูลคุณควรเรียกใช้เมธอด openDatabase ()ของคลาสDatabaseManager ภายในวิธีนี้เรามีตัวนับซึ่งระบุจำนวนฐานข้อมูลที่ถูกเปิด ถ้ามันเท่ากับหนึ่งนั่นหมายความว่าเราจำเป็นต้องสร้างการเชื่อมต่อฐานข้อมูลใหม่ถ้าไม่เชื่อมต่อฐานข้อมูลถูกสร้างขึ้นแล้ว
เช่นเดียวกับที่เกิดขึ้นในcloseDatabase ()วิธีการ ทุกครั้งที่เราเรียกเมธอดนี้ตัวนับจะลดลงเมื่อใดก็ตามที่เป็นศูนย์เราจะปิดการเชื่อมต่อฐานข้อมูล
ตอนนี้คุณควรจะสามารถใช้ฐานข้อมูลของคุณและตรวจสอบให้แน่ใจว่ามันปลอดภัย