안드로이드에서 데이터베이스 스레드 보안 문제

10491 단어 데이터베이스
하나.개술
실제 응용에서는 여러 스레드가 동시에 데이터베이스에 액세스할 때 몇 가지 예외가 발생할 수 있습니다. 먼저 어떤 예외가 발생할지 살펴보겠습니다.
만약 우리가 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

새 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 synchronized SQLiteDatabase getDatabase() {
        return mDatabaseHelper.getWritableDatabase();
    }

}

그리고 저희가 데이터베이스에 접근한 코드를 다음과 같이 바꿨어요.
// In your application class
 DatabaseManager.initializeInstance(new DatabaseHelper());

 // 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 대상을 되돌려줍니다. 이 때 어떤 상황이 발생합니까?루틴 1은 데이터베이스 연결을 닫았을 수도 있지만 루틴 2는 여전히 사용되고 있어 불법 상태 이상을 초래했다.
우리는 데이터베이스를 사용하는 사람이 없는지 확인한 후에야 닫을 수 있다
다음은 최종 실현 코드를 살펴보자.
public class DatabaseManager {

    private AtomicInteger mOpenCounter = new AtomicInteger();

    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() {
        //incrementAndGet()              1   
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
    //decrementAndGet()              1   
        if(mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();

        }
    }
}

그리고 이렇게 사용해요.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
    database.insert(...);
    // database.close(); Don't close it directly!
    DatabaseManager.getInstance().closeDatabase(); // correct way

데이터베이스 대상이 필요할 때마다 데이터베이스 관리자의 오픈 데이터베이스 방법을 호출합니다. 이 방법에는 데이터베이스가 몇 번 열렸는지 표시하는 계수기가 있습니다. 만약 계수기의 값이 1이라면 데이터베이스 대상을 만들어야 합니다. 그렇지 않으면 데이터베이스가 이미 만들어졌음을 설명합니다.
마찬가지로closeDatabase 방법에서 우리가 이 방법을 호출할 때마다 계수기의 값은 1이 줄어들고 계수기의 값이 0일 때 우리는 데이터베이스를 닫아야 한다.
둘.총결산
다음은 다중 스레드 조건에서 데이터베이스를 읽고 쓰는 비정상적인 상황을 요약해 보겠습니다.
1. SQLiteOpenHelper를 사용하는 멀티스레드 쓰기또한 SQLiteDatabase를 사용하는 멀티스레드가 보장됩니다.이상을 일으키지 않습니다.2. 다중 스레드 쓰기, 다중 SQLiteOpenHelper를 사용하면 삽입 시 이상이 발생하여 삽입 오류가 발생할 수 있습니다.
E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

3.android 프레임워크, 다중 루틴 쓰기 데이터베이스의 로컬 방법에는 동기화 잠금 보호가 없고 쓰기는 이상을 던집니다.
따라서 다중 스레드 쓰기는 동일한 SQLiteOpenHelper 객체를 사용해야 합니다.
4. 다중 스레드 읽기, 읽기 사이에 동기화 자물쇠가 없고 각 스레드마다 각각의 SQLiteOpenHelper 대상을 사용해야 합니다. 테스트를 통해 문제가 없습니다. 4.다중 스레드 읽기와 쓰기, 다중 스레드 쓰기 전에 이미 결과를 알았기 때문에 같은 시간에 한 글자만 쓸 수 있다.다중 스레드를 동시에 읽을 수 있기 때문에 다음 정책을 사용하십시오: 하나의 스레드를 쓰고 여러 스레드를 동시에 읽으며 모든 스레드는 각각의 SQLiteOpenHelper를 사용합니다.이때 예외가 발생합니다.
E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407 
E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 

이상을 삽입하면 라인이 읽을 때 데이터베이스를 쓰면 이상을 던진다는 뜻이다
그러면 어떻게 한 라인의 쓰기, 여러 라인의 읽기를 실현합니까?
사실 SQLiteDataBase는 API 11에 ENABLE 속성을 추가했습니다.WRITE_AHEAD_LOGGING
enable Write AheadLogging을 사용하여 이 속성을 열고disable Write AheadLogging을 닫으면 이 속성이 무슨 뜻입니까?
api 문서를 참고하십시오. 이 속성이 닫혔을 때 읽기, 쓰기를 동시에 할 수 없습니다. 자물쇠를 통해 보장합니다.
간단하게 말하면, enable Write AheadLogging () 과disable Write AheadLogging () 을 호출하면 이 데이터가 다중 루틴으로 읽기와 쓰기를 실행하는지 여부를 제어할 수 있으며, 허용되면, 한 개의 루틴과 여러 개의 읽기 루틴이 SQLite Database에서 동시에 작동하도록 허용합니다.실현 원리는 쓰기 작업은 사실 하나의 단독log 파일이고 읽기 작업은 원 데이터 파일을 읽으며 쓰기 작업이 시작되기 전의 내용을 써서 서로 영향을 주지 않는다는 것이다.쓰기 작업이 끝난 후에 읽기 작업이 새 데이터베이스의 상태를 감지할 것입니다.물론 이렇게 하는 폐단은 더 많은 메모리 공간을 소모하는 것이다.

좋은 웹페이지 즐겨찾기