Параллельный доступ к базе данных
Та же статья в моем блоге (мне больше нравится форматирование)
Я написал небольшую статью, в которой описывается, как сделать доступ к вашей базе данных 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, вы фактически устанавливаете новое соединение с базой данных. Если вы попытаетесь одновременно выполнить запись в базу данных из разных соединений, произойдет сбой. (из ответа выше)
Чтобы использовать базу данных с несколькими потоками, нам нужно убедиться, что мы используем одно соединение с базой данных.
Давайте создадим одноэлементный класс 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 .
Мы должны убедиться, что никто не использует базу данных, и только потом закрывать ее. Некоторым людям по стеку рекомендуется не закрывать вашу базу данных SQLite . Это приведет к следующему сообщению 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 () . Каждый раз, когда мы вызываем этот метод, счетчик уменьшается, когда он равен нулю, мы закрываем соединение с базой данных.
Теперь вы должны иметь возможность использовать свою базу данных и быть уверенным, что она безопасна для потоков.