同時データベースアクセス
私のブログの同じ記事(もっとフォーマットするのが好きです)
私は、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に次のメッセージが表示され、変更の1つが書き込まれません。
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
これは、新しいSQLiteOpenHelperオブジェクトを作成するたびに、実際に新しいデータベース接続を行うために発生します。実際の個別の接続から同時にデータベースに書き込もうとすると、失敗します。(上記の回答から)
複数のスレッドでデータベースを使用するには、1つのデータベース接続を使用していることを確認する必要があります。
単一の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
データベース接続を1つだけ使用しているため、メソッドgetDatabase()は、Thread1およびThread2に対してSQLiteDatabaseオブジェクトの同じインスタンスを返します。何が起こっているのか、Thread2がまだ使用している間に、Thread1がデータベースを閉じる場合があります。そのため、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
データベースが必要になるたびに、DatabaseManagerクラスのopenDatabase()メソッドを呼び出す必要があります。このメソッドの内部には、データベースが開かれた回数を示すカウンターがあります。それが1に等しい場合、新しいデータベース接続を作成する必要があることを意味します。そうでない場合、データベース接続はすでに作成されています。
同じことがcloseDatabase()メソッドでも起こります。このメソッドを呼び出すたびに、counterが減少し、それがゼロになるたびに、データベース接続を閉じています。
これでデータベースを使用でき、スレッドセーフであることを確認できます。