Android에서 SQLite에 대한 모범 사례는 무엇입니까?


694

Android 앱의 SQLite 데이터베이스에서 쿼리를 실행할 때 모범 사례로 간주되는 것은 무엇입니까?

AsyncTask의 doInBackground에서 삽입, 삭제 및 선택 쿼리를 실행하는 것이 안전합니까? 아니면 UI 스레드를 사용해야합니까? 데이터베이스 쿼리가 "무거울"수 있고 앱을 잠글 수 있으므로 UI ​​스레드를 사용하지 않아야한다고 가정합니다 ( 응용 프로그램 응답 없음 ).

여러 AsyncTask가있는 경우 연결을 공유해야합니까, 아니면 각각 연결을 열어야합니까?

이러한 시나리오에 대한 모범 사례가 있습니까?


10
무엇을 하든지, 컨텐츠 제공자 (또는 SQLite 인터페이스)가 공개적으로 직면 한 경우 입력을 삭제해야합니다!
Kristopher Micinski

37
UI 스레드에서 db 액세스를해서는 안됩니다.
Edward Falk

@EdwardFalk 왜 안돼? 확실히 이것이 유효한 사용 사례가 있습니까?
Michael

4
UI 스레드에서 I / O, 네트워크 액세스 등을 수행하면 작업이 완료 될 때까지 전체 장치가 정지됩니다. 1/20 초 안에 완료되면 괜찮습니다. 시간이 더 걸리면 나쁜 사용자 경험이 있습니다.
Edward Falk

답변:


631

삽입, 업데이트, 삭제 및 읽기는 일반적으로 여러 스레드에서 정상이지만 Brad의 대답 은 정확하지 않습니다. 연결을 만들고 사용하는 방법에주의해야합니다. 데이터베이스가 손상되지 않은 경우에도 업데이트 호출이 실패하는 상황이 있습니다.

기본 답변.

SqliteOpenHelper 객체는 하나의 데이터베이스 연결을 유지합니다. 읽기 및 쓰기 연결을 제공하는 것처럼 보이지만 실제로는 그렇지 않습니다. 읽기 전용을 호출하면 관계없이 쓰기 데이터베이스 연결이 제공됩니다.

따라서 하나의 도우미 인스턴스, 하나의 db 연결. 여러 스레드에서 사용하더라도 한 번에 하나의 연결입니다. SqliteDatabase 객체는 액세스를 직렬화하기 위해 java 잠금을 사용합니다. 따라서 100 개의 스레드에 하나의 db 인스턴스가 있으면 실제 온 디스크 데이터베이스에 대한 호출이 직렬화됩니다.

따라서 하나의 도우미, 하나의 db 연결은 Java 코드로 직렬화됩니다. 하나의 스레드, 1000 개의 스레드. 그들 사이에 공유 된 하나의 헬퍼 인스턴스를 사용하는 경우 모든 DB 액세스 코드는 직렬입니다. 그리고 인생은 좋습니다 (ish).

실제 고유 연결에서 데이터베이스에 동시에 쓰려고하면 실패합니다. 첫 번째 작업이 완료 될 때까지 기다렸다가 쓸 수 없습니다. 단순히 변경 사항을 쓰지 않습니다. 게다가 SQLiteDatabase에서 올바른 버전의 삽입 / 업데이트를 호출하지 않으면 예외가 발생하지 않습니다. LogCat에 메시지가 표시 될 것입니다.

그래서 여러 스레드? 하나의 도우미를 사용하십시오. 기간. 하나의 스레드 만 작성한다는 것을 알고 있다면 여러 개의 연결을 사용할 수 있으며 읽기 속도는 빨라지지만 구매자는주의해야합니다. 나는 그렇게 많이 테스트하지 않았습니다.

보다 자세한 내용과 예제 앱이 포함 된 블로그 게시물이 있습니다.

그레이와 저는 Ormlite를 기반으로하는 ORM 툴을 실제로 정리하고 있는데,이 툴은 Android 데이터베이스 구현에서 기본적으로 작동하며 블로그 포스트에서 설명하는 안전한 생성 / 호출 구조를 따릅니다. 그것은 곧 나올 것입니다. 구경하다.


그 동안 후속 블로그 게시물이 있습니다.

또한 앞에서 언급 한 잠금 예제의 2point0 으로 포크를 점검하십시오 .


2
여담으로, Ormlite의 안드로이드 지원에서 찾을 수 있습니다 ormlite.sourceforge.net/sqlite_java_android_orm.html . 샘플 프로젝트, 문서 및 항아리가 있습니다.
그레이

1
두 번째는 제쳐두고 ormlite 코드에는 dbhelper 인스턴스를 관리하는 데 사용할 수있는 도우미 클래스가 있습니다. ormlite를 사용할 수 있지만 필수는 아닙니다. 헬퍼 클래스를 사용하여 연결 관리를 수행 할 수 있습니다.
Kevin Galligan

31
카 기이, 자세한 설명 감사합니다. 한 가지만 분명히 설명해 주시겠습니까? 하나의 도우미가 필요하지만 하나의 연결 (예 : SqliteDatabase 개체)도 있어야한다고 생각합니다. 즉, 얼마나 자주 getWritableDatabase를 호출해야합니까? close ()를 언제 호출합니까?
Artem

3
코드를 업데이트했습니다. 블로그 호스트를 전환했을 때 원본을 잃어 버렸지 만 문제를 보여줄 예제 코드를 추가했습니다. 또한 단일 연결을 어떻게 관리합니까? 처음에는 훨씬 더 복잡한 해결책이 있었지만 그 이후로 수정했습니다. 여기를보십시오 : touchlab.co/uncategorized/single-sqlite-connection
Kevin Galligan

연결을 폐기 할 필요가 있으며 어디서해야합니까?
tugce

189

동시 데이터베이스 액세스

내 블로그에서 같은 기사

안드로이드 데이터베이스 스레드에 안전하게 액세스하는 방법을 설명하는 작은 기사를 썼습니다.


자신의 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 () 메소드 는 Thread1Thread2에 대해 동일한 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이 될 때마다 데이터베이스 연결이 닫힙니다.


이제 데이터베이스를 사용할 수 있고 스레드 안전 상태 여야합니다.


10
나는 당신이 db를 닫지 말 것을 제안하는 사람들 중 하나입니다. db를 열면 닫지 말고 다시 열려고하면 "누설 발견"오류 만 발생합니다. 당신은 단지 하나의 개방 도우미 및 사용하는 경우 결코 가까운 DB를 해당 오류가 발생하지 않습니다. 다른 방법을 찾으면 코드로 알려주십시오. 어딘가에 더 긴 게시물이 있지만 찾을 수 없습니다. SO 포인트 부서에서 우리 모두를 능가하는 commonsware의 질문 : stackoverflow.com/questions/7211941/…
Kevin Galligan

4
더 많은 생각. # 1, 나는 당신의 관리자 내부에 도우미를 만들 것입니다. 외부에 문제가 있는지 묻습니다. 새로운 개발자는 미친 이유로 도우미를 직접 호출 할 수 있습니다. 또한 init 메소드가 필요한 경우 인스턴스가 이미 존재하면 예외를 처리하십시오. 다중 DB 앱은 그대로 실패합니다. # 2, 왜 mDatabase 필드입니까? 도우미에서 사용할 수 있습니다. # 3, "never close"를 향한 첫 걸음으로, 앱이 다운되고 "닫히지"않았을 때 DB는 어떻게 되나요? 힌트, 아무것도 SQLite는 매우 안정적이므로 괜찮습니다. 이것이 닫을 필요가 없는지 알아내는 1 단계였습니다 .
Kevin Galligan

1
인스턴스를 가져 오기 전에 공개 초기화 메소드를 사용하여 호출하는 이유는 무엇입니까? 개인 생성자가없는 이유는 무엇 if(instance==null)입니까? 매번 초기화를 호출하는 것 외에는 선택의 여지가 없습니다. 다른 응용 프로그램 등에서 초기화되었는지 아닌지 어떻게 알 수 있습니까?
ChiefTwoPencils

1
initializeInstance()유형의 매개 변수가 SQLiteOpenHelper있지만 주석에서 사용하도록 언급했습니다 DatabaseManager.initializeInstance(getApplicationContext());. 무슨 일이야? 이것이 어떻게 작동 할 수 있습니까?
faizal

2
@DmytroDanylyk "DatabaseManager는 스레드 안전 싱글 톤이므로 어디에서나 사용할 수 있습니다"는 virsir의 질문에 대한 대답은 아닙니다. 객체는 프로세스 간 공유되지 않습니다. 데이터베이스 관리자는 동기화 어댑터 (android : process = ": sync")와 같은 다른 프로세스에서 상태가 없습니다.
whizzle

17
  • Thread또는 AsyncTask장기 실행 작업 (50ms +)에 또는 을 사용하십시오 . 앱을 테스트하여 어디에 있는지 확인하십시오. 대부분의 작업 (아마도)에는 스레드가 필요하지 않습니다. 대부분의 작업 (아마도)에는 몇 개의 행만 포함되기 때문입니다. 대량 작업에는 스레드를 사용하십시오.
  • SQLiteDatabase스레드간에 디스크의 각 DB에 대해 하나의 인스턴스를 공유 하고 열린 연결을 추적하는 계산 시스템을 구현하십시오.

이러한 시나리오에 대한 모범 사례가 있습니까?

모든 클래스간에 정적 필드를 공유하십시오. 나는 그와 다른 것들을 공유하기 위해 싱글 톤을 유지했습니다. 데이터베이스를 조기에 닫거나 열지 않은 상태로 두려면 계산 체계 (일반적으로 AtomicInteger를 사용)를 사용해야합니다.

내 해결책 :

최신 버전은 https://github.com/JakarCo/databasemanager를 참조 하십시오. 그러나 여기에서도 코드를 최신 상태로 유지하려고합니다. 내 솔루션을 이해하려면 코드를보고 내 노트를 읽으십시오. 내 노트는 일반적으로 매우 유용합니다.

  1. 코드를이라는 새 파일에 복사 / 붙여 넣기하십시오 DatabaseManager. (또는 github에서 다운로드하십시오)
  2. 평소처럼 확장 DatabaseManager하고 구현 onCreate하며 디스크에 다른 데이터베이스를 갖기 위해 onUpgradeDatabaseManager클래스 의 여러 서브 클래스를 작성할 수 있습니다 .
  3. 서브 클래스를 인스턴스화 getDb()하고 SQLiteDatabase클래스 를 사용하도록 호출하십시오 .
  4. close()인스턴스화 한 각 서브 클래스 호출

복사 / 붙여 넣기 코드 :

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import java.util.concurrent.ConcurrentHashMap;

/** Extend this class and use it as an SQLiteOpenHelper class
 *
 * DO NOT distribute, sell, or present this code as your own. 
 * for any distributing/selling, or whatever, see the info at the link below
 *
 * Distribution, attribution, legal stuff,
 * See https://github.com/JakarCo/databasemanager
 * 
 * If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
 * 
 * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
 *
 * This is a simple database manager class which makes threading/synchronization super easy.
 *
 * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
 *  Instantiate this class once in each thread that uses the database. 
 *  Make sure to call {@link #close()} on every opened instance of this class
 *  If it is closed, then call {@link #open()} before using again.
 * 
 * Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
 *
 * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
 * 
 *
 */
abstract public class DatabaseManager {

    /**See SQLiteOpenHelper documentation
    */
    abstract public void onCreate(SQLiteDatabase db);
    /**See SQLiteOpenHelper documentation
     */
    abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    /**Optional.
     * *
     */
    public void onOpen(SQLiteDatabase db){}
    /**Optional.
     * 
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
    /**Optional
     * 
     */
    public void onConfigure(SQLiteDatabase db){}



    /** The SQLiteOpenHelper class is not actually used by your application.
     *
     */
    static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {

        DatabaseManager databaseManager;
        private AtomicInteger counter = new AtomicInteger(0);

        public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
            super(context, name, null, version);
            this.databaseManager = databaseManager;
        }

        public void addConnection(){
            counter.incrementAndGet();
        }
        public void removeConnection(){
            counter.decrementAndGet();
        }
        public int getCounter() {
            return counter.get();
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            databaseManager.onCreate(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onUpgrade(db, oldVersion, newVersion);
        }

        @Override
        public void onOpen(SQLiteDatabase db) {
            databaseManager.onOpen(db);
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onDowngrade(db, oldVersion, newVersion);
        }

        @Override
        public void onConfigure(SQLiteDatabase db) {
            databaseManager.onConfigure(db);
        }
    }

    private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();

    private static final Object lockObject = new Object();


    private DBSQLiteOpenHelper sqLiteOpenHelper;
    private SQLiteDatabase db;
    private Context context;

    /** Instantiate a new DB Helper. 
     * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
     *
     * @param context Any {@link android.content.Context} belonging to your package.
     * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
     * @param version the database version.
     */
    public DatabaseManager(Context context, String name, int version) {
        String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
        synchronized (lockObject) {
            sqLiteOpenHelper = dbMap.get(dbPath);
            if (sqLiteOpenHelper==null) {
                sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                dbMap.put(dbPath,sqLiteOpenHelper);
            }
            //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
            db = sqLiteOpenHelper.getWritableDatabase();
        }
        this.context = context.getApplicationContext();
    }
    /**Get the writable SQLiteDatabase
     */
    public SQLiteDatabase getDb(){
        return db;
    }

    /** Check if the underlying SQLiteDatabase is open
     *
     * @return whether the DB is open or not
     */
    public boolean isOpen(){
        return (db!=null&&db.isOpen());
    }


    /** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
     *  <br />If the new counter is 0, then the database will be closed.
     *  <br /><br />This needs to be called before application exit.
     * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
     *
     * @return true if the underlying {@link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
     */
    public boolean close(){
        sqLiteOpenHelper.removeConnection();
        if (sqLiteOpenHelper.getCounter()==0){
            synchronized (lockObject){
                if (db.inTransaction())db.endTransaction();
                if (db.isOpen())db.close();
                db = null;
            }
            return true;
        }
        return false;
    }
    /** Increments the internal db counter by one and opens the db if needed
    *
    */
    public void open(){
        sqLiteOpenHelper.addConnection();
        if (db==null||!db.isOpen()){
                synchronized (lockObject){
                    db = sqLiteOpenHelper.getWritableDatabase();
                }
        } 
    }
}

1
"닫기"라고 전화 한 다음 수업을 재사용하려고하면 어떻게됩니까? 충돌합니까? 아니면 DB를 다시 사용할 수 있도록 자동으로 다시 초기화됩니까?
안드로이드 개발자

1
@androiddeveloper, 호출 하면 클래스의 동일한 인스턴스를 사용하기 전에 다시 close호출 open하거나 새 인스턴스를 만들 수 있습니다. close코드에서, 내가 설정 했기 때문에 (널 값이므로) db=null에서 반환 값을 사용할 수 getDb없으므로 NullPointerException다음과 같은 작업 myInstance.close(); myInstance.getDb().query(...);
Reed

왜 결합하지 getDb()open()하나의 방법으로?
Alex Burdusel

@Burdu, 데이터베이스 카운터 및 잘못된 디자인을 추가로 제어 할 수있는 조합. 그래도 최선의 방법은 아닙니다. 며칠 후에 업데이트하겠습니다.
Reed

@ Burdu, 방금 업데이트했습니다. 여기서 새 코드를 얻을 수 있습니다 . 나는 그것을 테스트하지 않았으므로 변경 사항을 커밋 해야하는지 알려주십시오.
Reed

11

데이터베이스는 멀티 스레딩으로 매우 유연합니다. 내 앱은 여러 스레드에서 동시에 DB를 공격했으며 정상적으로 작동합니다. 어떤 경우에는 DB에 동시에 여러 프로세스가 충돌하여 작동합니다.

비동기 작업-가능한 경우 동일한 연결을 사용하지만 필요한 경우 다른 작업에서 DB에 액세스해도 괜찮습니다.


또한 서로 다른 연결로 독자와 작가가 있습니까 아니면 단일 연결을 공유해야합니까? 감사.
회색

@Gray-맞습니다. 명시 적으로 언급해야합니다. 연결에 관해서는 가능한 한 동일한 연결을 사용하지만 잠금은 파일 시스템 수준에서 처리되므로 코드에서 여러 번 열 수는 있지만 가능한 한 단일 연결을 사용합니다. Android sqlite DB는 매우 유연하고 용서합니다.
Brad Hein

3
@Gray,이 방법을 사용하는 사람들을 위해 업데이트 된 정보를 게시하고 싶었습니다. 문서에 따르면 : 이 방법은 아무 것도 수행하지 않습니다. 사용하지 마세요.
Pijusn

3
이 방법이 끔찍하게 실패하는 것을 발견했습니다. 여러 응용 프로그램에서 액세스하기 위해 ContentProvider로 전환하고 있습니다. 우리는 방법에 대해 약간의 동시성을 수행해야하지만 동시에 데이터에 액세스하는 프로세스의 모든 문제를 해결해야합니다.
JPM

2
나는 이것이 오래되었다는 것을 알고 있지만 잘못되었습니다. 다른 s / s 의 다른 객체에서 DB에 액세스하는 것이 좋을 수도 있지만 때로는 오류가 발생하여 SQLiteDatabase (1297 줄)가 s를 사용 합니다.SQLiteDatabaseAsyncTaskThreadLock
Reed

7

Dmytro의 대답은 제 경우에 잘 작동합니다. 함수를 동기화 된 것으로 선언하는 것이 좋습니다. 적어도 내 경우에는 그렇지 않으면 null 포인터 예외를 호출합니다. 예를 들어 getWritableDatabase가 아직 한 스레드에서 반환되지 않고 openDatabse가 다른 스레드 동안 호출되었습니다.

public synchronized SQLiteDatabase openDatabase() {
    if(mOpenCounter.incrementAndGet() == 1) {
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    return mDatabase;
}

mDatabaseHelper.getWritableDatabase (); 이 새로운 데이터베이스 객체 생성되지 않습니다
외곽

5

몇 시간 동안이 문제로 어려움을 겪은 후 db 실행마다 하나의 db helper 객체 만 사용할 수 있음을 발견했습니다. 예를 들어

for(int x = 0; x < someMaxValue; x++)
{
    db = new DBAdapter(this);
    try
    {

        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }
    db.close();
}

에 따라 :

db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{

    try
    {
        // ask the database manager to add a row given the two strings
        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }

}
db.close();

루프가 반복 될 때마다 새 DBAdapter를 작성하는 것이 도우미 클래스를 통해 문자열을 데이터베이스로 가져올 수있는 유일한 방법이었습니다.


4

SQLiteDatabase API에 대한 이해는 다중 스레드 응용 프로그램이있는 경우 단일 데이터베이스를 가리키는 둘 이상의 SQLiteDatabase 개체를 가질 여유가 없다는 것입니다.

다른 스레드 / 프로세스 (너무)가 다른 SQLiteDatabase 개체 (JDBC 연결에서 사용하는 방식)를 사용하기 시작하면 개체를 확실히 만들 수 있지만 삽입 / 업데이트가 실패합니다.

여기서 유일한 해결책은 하나의 SQLiteDatabase 객체를 고수하고 startTransaction ()이 둘 이상의 스레드에서 사용될 때마다 Android는 여러 스레드에서 잠금을 관리하고 한 번에 하나의 스레드 만 독점 업데이트 액세스 권한을 갖도록 허용합니다.

또한 데이터베이스에서 "읽기"를 수행하고 다른 스레드에서 동일한 SQLiteDatabase 개체를 사용할 수 있으며 (다른 스레드가 쓰는 동안) 데이터베이스 손상이 발생하지 않습니다. 즉 "읽기 스레드"는 데이터베이스에서 데이터를 읽을 때까지 " 쓰레드는 "같은 SQLiteDatabase 객체를 사용하지만 데이터를 커밋합니다.

이는 읽기 스레드와 쓰기 스레드간에 연결 개체를 전달 (동일하게 사용)하면 커밋되지 않은 데이터도 인쇄하는 JDBC의 연결 개체와 다릅니다.

엔터프라이즈 응용 프로그램에서 BG 스레드가 SQLiteDatabase 개체를 독점적으로 보유하는 동안 UI 스레드가 기다릴 필요가 없도록 조건부 검사를 사용하려고합니다. UI 동작을 예측하고 BG 스레드가 'x'초 동안 실행되지 않도록합니다. 또한 UI 스레드가 먼저 가져 오도록 PrioriteQueue를 유지하여 SQLiteDatabase Connection 객체를 전달하는 것을 관리 할 수 ​​있습니다.


그리고 PriorityQueue에 리스너 (데이터베이스 객체를 가져 오려는) 또는 SQL 쿼리에 무엇을 넣습니까?
Pijusn

우선 순위 대기열 방식을 사용하지 않고 본질적으로 "호출자"스레드를 사용했습니다.
Swaroop

@Swaroop : PCMIIW을 "read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object. "쓰레드 쓰기"직후에 "쓰레드 읽기"를 시작하면 새로 업데이트 된 데이터 (쓰기 쓰레드에 삽입되거나 업데이트 됨)를 얻지 못할 수 있습니다. 읽기 스레드는 쓰기 스레드를 시작하기 전에 데이터를 읽을 수 있습니다. 쓰기 작업이 독점 잠금 대신 예약 잠금을 처음 활성화하기 때문에 발생합니다.
Amit Vikram Singh

4

Google I / O 2017에서 발표 된 새로운 아키텍처 접근 방식을 적용 할 수 있습니다 .

또한 Room 이라는 새로운 ORM 라이브러리도 포함합니다

@Entity, @Dao 및 @Database의 세 가지 주요 구성 요소가 포함되어 있습니다.

User.java

@Entity
public class User {
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.
}

UserDao.java

@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);
}

AppDatabase.java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

이 방법을 제대로 처리하지 못하고 이러한 관계에 대한 해결 방법을 찾으려면 많은 코드를 작성해야하기 때문에 여러 N 대 N 관계를 가진 데이터베이스에 대한 방을 제안하지 않습니다.
AlexPad

3

몇 가지 문제가 있었는데, 내가 왜 잘못되었는지 이해했다고 생각합니다.

getWriteableDatabase라는 close()미러로 헬퍼 클로즈 를 포함하는 데이터베이스 래퍼 클래스를 작성한 open()다음로 마이그레이션했습니다 ContentProvider. 코드가 사용하기 때문에 큰 단서라고 생각하는 모델을 ContentProvider사용하지 않는 경우 SQLiteDatabase.close()가 있습니다 getWriteableDatabase. 경우에 따라 직접 액세스 (주요 화면 유효성 검사 쿼리)를 수행하여 getWriteableDatabase / rawQuery 모델로 마이그레이션했습니다.

나는 싱글 톤을 사용하고 가까운 문서에는 약간 불길한 의견이 있습니다.

닫고 모든 열려있는 데이터베이스 개체를

(나의 대담한).

따라서 백그라운드 스레드를 사용하여 데이터베이스에 액세스하는 경우 간헐적 충돌이 발생했으며 포 그라운드와 동시에 실행됩니다.

따라서 close()참조를 보유하는 다른 스레드에 관계없이 데이터베이스를 강제로 닫습니다. 따라서 close()자체적으로 단순히 일치를 취소하는 getWriteableDatabase것이 아니라 열린 요청을 강제로 닫는 입니다. 코드가 단일 스레딩이므로 대부분의 경우 문제가되지 않지만 다중 스레드의 경우 항상 동기화를 열고 닫을 수있는 기회가 있습니다.

SqLiteDatabaseHelper 코드 인스턴스의 수를 설명하는 다른 곳에서 주석을 읽은 후 닫기를 원하는 유일한 시점은 백업 사본을 수행하려는 상황을 원하고 모든 연결을 강제로 닫고 SqLite를 강제로 실행하려는 경우입니다. 약탈 할 수있는 캐시 된 내용을 작성하십시오. 즉, 모든 애플리케이션 데이터베이스 활동을 중지하고, 헬퍼가 추적을 잃어버린 경우를 대비하여 닫고, 파일 레벨 활동 (백업 / 복원)을 수행 한 후 다시 시작하십시오.

제어 된 방식으로 시도하고 닫는 것이 좋은 생각처럼 들리지만 실제로 Android는 VM을 휴지통에 버릴 수있는 권리를 보유하므로 닫으면 캐시 된 업데이트가 작성되지 않을 위험이 줄어 듭니다. 스트레스를 받고 커서와 데이터베이스에 대한 참조 (정적 구성원이 아니어야 함)를 올바르게 해제 한 경우 도우미는 데이터베이스를 닫았습니다.

그래서 내 접근 방식은 다음과 같습니다.

getWriteableDatabase를 사용하여 싱글 톤 랩퍼에서여십시오. (나는 파생 된 응용 프로그램 클래스를 사용하여 컨텍스트의 필요성을 해결하기 위해 정적에서 응용 프로그램 컨텍스트를 제공했습니다).

직접 연락하지 마십시오.

결과 범위가 분명하지 않은 개체에 결과 데이터베이스를 저장하지 말고 암시 적 close ()를 트리거하기 위해 참조 횟수에 의존하십시오.

파일 레벨 처리를 수행하는 경우, 적절한 트랜잭션을 작성한다고 가정 할 때 런 어웨이 스레드가있는 경우를 대비하여 모든 데이터베이스 활동을 중지 한 다음 닫기를 호출하십시오. 잠재적으로 부분 트랜잭션의 파일 레벨 사본보다.


0

응답이 늦다는 것을 알고 있지만 안드로이드에서 sqlite 쿼리를 실행하는 가장 좋은 방법은 사용자 정의 컨텐츠 공급자를 사용하는 것입니다. 이러한 방식으로 UI는 데이터베이스 클래스 (SQLiteOpenHelper 클래스를 확장하는 클래스)와 분리됩니다. 또한 쿼리는 백그라운드 스레드 (커서 로더)에서 실행됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.