동시 데이터베이스 액세스
내 블로그에서 같은 기사
안드로이드 데이터베이스 스레드에 안전하게 액세스하는 방법을 설명하는 작은 기사를 썼습니다.
자신의 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 () 메소드 는 Thread1 및 Thread2에 대해 동일한 SQLiteDatabase 오브젝트 인스턴스를 리턴 합니다 . 무슨 일이있어, 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
데이터베이스가 필요할 때마다 DatabaseManager 클래스 의 openDatabase () 메소드를 호출해야 합니다 . 이 방법에는 데이터베이스가 몇 번 열렸는지 나타내는 카운터가 있습니다. 1과 같으면 새 데이터베이스 연결을 만들어야합니다. 그렇지 않은 경우 데이터베이스 연결이 이미 만들어져있는 것입니다.
closeDatabase () 메소드 에서도 마찬가지 입니다. 이 메소드를 호출 할 때마다 카운터가 줄어들고 0이 될 때마다 데이터베이스 연결이 닫힙니다.
이제 데이터베이스를 사용할 수 있고 스레드 안전 상태 여야합니다.