Одночасний доступ до бази даних
Та сама стаття в моєму блозі (мені більше подобається форматування)
Я написав невелику статтю, в якій описую, як зробити безпечний доступ до вашої бази даних 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();
Ви отримаєте наступне повідомлення у вашій реєстраційній системі, і одна із змін не буде записана.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Це відбувається тому, що кожного разу, коли ви створюєте новий об'єкт SQLiteOpenHelper, ви фактично встановлюєте нове підключення до бази даних. Якщо ви спробуєте одночасно записати в базу даних фактичні розрізнені з'єднання, це не вдасться. (з відповіді вище)
Щоб використовувати базу даних з декількома потоками, нам потрібно переконатися, що ми використовуємо одне підключення до бази даних.
Давайте зробимо одиночний клас Database Manager, який буде утримувати і повертати один об'єкт 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 () . Кожен раз, коли ми викликаємо цей метод, лічильник зменшується, коли він переходить до нуля, ми закриваємо підключення до бази даних.
Тепер ви повинні мати можливість використовувати вашу базу даних і бути впевненим, що це безпечно для потоків.