AndroidでのSQLiteのベストプラクティスは何ですか?


694

Androidアプリ内のSQLiteデータベースでクエリを実行する場合、何がベストプラクティスと見なされますか?

AsyncTaskのdoInBackgroundから挿入、削除、選択クエリを実行しても安全ですか?または、UIスレッドを使用する必要がありますか?データベースクエリは「重い」可能性があり、アプリをロックする可能性があるためUIスレッドを使用しないでください。その結果、アプリケーションが応答しなくなります(ANR)。

複数のAsyncTaskがある場合、それらは接続を共有する必要がありますか、それともそれぞれに接続を開く必要がありますか?

これらのシナリオのベストプラクティスはありますか?


10
コンテンツプロバイダー(またはSQLiteインターフェイス)が公開されている場合は、何をする場合でも入力を無害化することを忘れないでください!
Kristopher Micinski

37
あなたは間違いなくUIスレッドからdbアクセスを行うべきではありません。
エドワードフォーク

@EdwardFalkどうして?これを行うことが有効なユースケースは確かにありますか?
マイケル

4
UIスレッドからI / Oやネットワークアクセスなどを行うと、操作が完了するまでデバイス全体がフリーズします。1/20秒で完了する場合は問題ありません。時間がかかる場合は、ユーザーエクスペリエンスが低下しています。
エドワードフォーク

回答:


632

挿入、更新、削除、読み取りは通常、複数のスレッドからは問題ありませんが、Bradの答えは正しくありません。接続を作成して使用する方法には注意が必要です。データベースが破損していなくても、更新呼び出しが失敗する状況があります。

基本的な答え。

SqliteOpenHelperオブジェクトは、1つのデータベース接続を保持します。それはあなたに読み書き接続を提供するように見えますが、実際にはそうではありません。読み取り専用を呼び出すと、関係なく書き込みデータベース接続を取得します。

つまり、1つのヘルパーインスタンス、1つのdb接続です。複数のスレッドから使​​用する場合でも、一度に1つの接続。SqliteDatabaseオブジェクトは、Javaロックを使用して、アクセスのシリアル化を維持します。したがって、100個のスレッドに1つのdbインスタンスがある場合、実際のディスク上のデータベースへの呼び出しはシリアル化されます。

つまり、1つのヘルパーと1つのdb接続であり、Javaコードでシリアル化されます。1つのスレッド、1000スレッド、それらの間で共有される1つのヘルパーインスタンスを使用する場合、すべてのdbアクセスコードはシリアルです。そして、人生は良いものです。

実際の個別の接続から同時にデータベースに書き込もうとすると、失敗します。最初が完了するまで待機せず、次に書き込みます。それは単にあなたの変更を書きません。さらに悪いことに、SQLiteDatabaseで適切なバージョンの挿入/更新を呼び出さないと、例外が発生しません。LogCatにメッセージが表示されるだけです。

それで、マルチスレッド?1つのヘルパーを使用します。限目。書き込むスレッドが1つだけであることがわかっている場合は、複数の接続を使用できる場合があり、読み取りは速くなりますが、購入者は注意してください。私はそれほどテストしていません。

これは、はるかに詳細なブログ投稿とサンプルアプリです。

グレイと私は実際に、彼のOrmliteに基づいて、Androidデータベース実装でネイティブに動作し、ブログの投稿で説明した安全な作成/呼び出し構造に従うORMツールをまとめています。それはすぐに出るはずです。見てください。


それまでの間、フォローアップのブログ投稿があります:

また、前述のロックの例の2point0でフォークをチェックアウトします。


2
余談ですが、OrmliteのAndroidサポートはormlite.sourceforge.net/sqlite_java_android_orm.htmlにあります。サンプルプロジェクト、ドキュメント、jarがあります。
グレイ(

1
もう一つ。ormliteコードには、dbhelperインスタンスの管理に使用できるヘルパークラスがあります。ormliteのものを使用できますが、必須ではありません。接続管理を行うためだけにヘルパークラスを使用できます。
Kevin Galligan、2010年

31
高木井さん、詳しい説明ありがとうございます。一つ明確にしていただけませんか-ヘルパーは1つだけですが、接続も1つ(つまり、SqliteDatabaseオブジェクトが1つ)だけであることを理解していますか?つまり、どのくらいの頻度でgetWritableDatabaseを呼び出す必要がありますか?同様に重要なのは、いつclose()を呼び出すかです。
Artem

3
コードを更新しました。ブログのホストを切り替えたときにオリジナルは失われましたが、問題を示すいくつかのスリム化されたサンプルコードを追加しました。また、単一の接続をどのように管理しますか?最初ははるかに複雑な解決策がありましたが、それ以降修正しました。ここをご覧ください:touchlab.co/uncategorized/single-sqlite-connection
Kevin Galligan

接続を破棄する必要性とそれを行う場所はありますか?
tugce

187

同時データベースアクセス

私のブログの同じ記事(もっとフォーマットするのが好きです)

私は、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が減少し、それがゼロになるたびに、データベース接続を閉じています。


これでデータベースを使用でき、スレッドセーフであることを確認できます。


10
私はあなたがデータベースを決して閉じないことを提案する人々の一人です。「リークが見つかりました」というエラーが表示されるのは、データベースを開いて閉じずに、もう一度開いた場合のみです。あなたが唯一の単一のオープンヘルパー、および使用している場合は、決して近いデシベルを、あなたはそのエラーを得ることはありません。それ以外の場合は、(コードで)お知らせください。私はどこかでそれについてもっと長い投稿をしましたが、それを見つけることができません。:質問は誰一種outgunsたちの両方SOポイント部門での、ここcommonswareによって答えstackoverflow.com/questions/7211941/...
ケビンGalligan

4
より多くの考え。#1、私はあなたのマネージャーの中にヘルパーを作成します。外に持っていくために問題を求める。新しい開発者は、なんらかの理由でヘルパーを直接呼び出す場合があります。また、initメソッドが必要な場合、インスタンスがすでに存在していれば例外をスローします。マルチDBアプリは明らかにそのまま失敗します。#2、なぜmDatabaseフィールドなのか?ヘルパーから入手できます。#3、「クローズしない」に向けた最初のステップとして、アプリがクラッシュして「クローズ」しないと、dbはどうなりますか?ヒント、何も。SQLiteは非常に安定しているため、問題ありません。これが、なぜ閉じる必要がないのを理解するためのステップ1 でした。
Kevin Galligan、2014年

1
インスタンスを取得する前にパブリック初期化メソッドを使用して呼び出す理由は何ですか?呼び出されるプライベートコンストラクターがないのはなぜif(instance==null)ですか?毎回、initializeを呼び出す以外に選択肢はありません。それが他のアプリケーションなどで初期化されているかどうかを他にどのように知っていますか?
ChiefTwoPencils 2014

1
initializeInstance()型のパラメータがありますSQLiteOpenHelperが、コメントで使用するように言及しましたDatabaseManager.initializeInstance(getApplicationContext());。何が起こっている?これはどのように機能するのでしょうか?
ファイサル、2014年

2
@DmytroDanylyk「DatabaseManagerはスレッドセーフなシングルトンであるため、どこでも使用できる」virsirの質問への回答には当てはまりません。オブジェクトはプロセス間で共有されません。DatabaseManagerは、同期アダプター(android:process = ":sync")などの別のプロセスで状態を持ちません
ウィズル

17
  • 長時間実行する操作(50ミリ秒Thread以上)にAsyncTaskは、またはを使用します。アプリをテストして、それがどこにあるかを確認します。ほとんどの操作は(おそらく)スレッドを必要としません。これは、ほとんどの操作が(おそらく)数行しか含まないためです。一括操作にはスレッドを使用します。
  • SQLiteDatabaseスレッド間でディスク上の各DBの1つのインスタンスを共有し、カウントシステムを実装して、開いている接続を追跡します。

これらのシナリオのベストプラクティスはありますか?

すべてのクラス間で静的フィールドを共有します。私はそのために、そして共有する必要のある他のもののためにシングルトンを維持していました。カウント方式(通常はAtomicIntegerを使用)も、データベースを早期に閉じたり開いたままにしたりしないようにするために使用する必要があります。

私の解決策:

最新バージョンについては、https://github.com/JakarCo/databasemanagerを参照してください。ただし、ここでもコードを最新の状態に保つようにします。私のソリューションを理解したい場合は、コードを見て、私のメモを読んでください。私のメモは通常かなり役に立ちます。

  1. という名前の新しいファイルにコードをコピーして貼り付けますDatabaseManager。(またはgithubからダウンロード)
  2. 拡張DatabaseManagerして実装しonCreateonUpgrade通常のように。DatabaseManagerディスク上に異なるデータベースを持つために、1つのクラスの複数のサブクラスを作成できます。
  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
「close」を呼び出してクラスを再利用しようとするとどうなりますか?クラッシュしますか?それとも、DBを再び使用できるように自動的に再初期化されますか?
Android開発者

1
@androiddeveloper、を呼び出す場合は、クラスの同じインスタンスを使用する前に再度close呼び出すopen必要があります。または、新しいインスタンスを作成できます。closeコードでは、を設定しているので、(nullであるため)db=nullからの戻り値を使用できないgetDbため、NullPointerException次のような場合にを取得しますmyInstance.close(); myInstance.getDb().query(...);
Reed

なぜ組み合わせないgetDb()open()、単一のメソッドに?
Alex Burdusel、2015年

@Burdu、データベースカウンターと不適切な設計に対する追加の制御を提供することの組み合わせ。ただし、これが最善の方法とは限りません。数日で更新します。
リード

@Burdu、私はちょうどそれを更新しました。ここから新しいコードを取得できます。まだテストしていないので、変更をコミットする必要があるかどうかをお知らせください。
リード

11

データベースはマルチスレッドで非常に柔軟です。私のアプリは、多くの異なるスレッドから同時にDBにアクセスしましたが、問題はありません。場合によっては、複数のプロセスが同時にDBにアクセスし、それも正常に機能します。

非同期タスク-可能な場合は同じ接続を使用しますが、必要に応じて、別のタスクからDBにアクセスすることもできます。


また、リーダーとライターは別の接続にありますか、それとも単一の接続を共有する必要がありますか?ありがとう。
2010

@Gray-正解、私はそれを明示的に述べるべきだった。接続に関しては、可能な限り同じ接続を使用しますが、ロックはファイルシステムレベルで処理されるため、コードで複数回開くことができますが、できるだけ1つの接続を使用します。Android sqlite DBは非常に柔軟で寛容です。
Brad Hein

3
@Gray、この方法を使用している可能性のある人のために最新情報を投稿したかっただけです。ドキュメントは言う:このメソッドは今何もしません。使ってはいけません。
Pijusn 2012

3
このメソッドがひどく失敗することがわかったので、複数のアプリケーションからアクセスするためにContentProviderに切り替えます。メソッドでいくつかの同時実行を行う必要がありますが、すべてのデータに同時にアクセスするプロセスの問題が修正されるはずです。
JPM

2
これは古いことはわかっていますが、正しくありません。それは可能性が異なるからアクセスDBに大丈夫うまくSQLiteDatabase異なる上のオブジェクトAsyncTaskS / ThreadSが、それは時々理由であるエラーにつながるSQLiteDatabase(ライン1297)の使用LockS
リード

7

私の場合、Dmytroの回答は問題なく機能します。関数を同期として宣言する方が良いと思います。少なくとも私の場合、それ以外の場合はnullポインタ例外が呼び出されます。たとえば、getWritableDatabaseが1つのスレッドでまだ返されておらず、openDatabseが別のスレッドで呼び出されています。

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

mDatabaseHelper.getWritableDatabase(); これは新しいデータベースオブジェクトを作成しません
2014年

5

これに数時間苦労した後、dbの実行ごとに1つのdbヘルパーオブジェクトしか使用できないことがわかりました。例えば、

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接続での使用方法のように)、挿入/更新は失敗します。

ここでの唯一の解決策は、1つのSQLiteDatabaseオブジェクトを使用することであり、startTransaction()が複数のスレッドで使用される場合は常に、Androidは異なるスレッド間のロックを管理し、一度に1つのスレッドのみが排他的な更新アクセスを許可します。

また、データベースから「読み取り」を実行して、別のスレッドで(別のスレッドが書き込みを行う間)同じSQLiteDatabaseオブジェクトを使用することもできます。データベースが破損することはありません。つまり、「読み取りスレッド」は、「読み取り」までデータベースからデータを読み取りません。書き込みスレッド "はデータをコミットしますが、どちらも同じSQLiteDatabaseオブジェクトを使用します。

これは、JDBCでの接続オブジェクトの方法とは異なります。JDBCでは、読み取りスレッドと書き込みスレッドの間で接続オブジェクトを渡す(同じものを使用する)と、コミットされていないデータも印刷される可能性があります。

私のエンタープライズアプリケーションでは、BGスレッドがSQLiteDatabaseオブジェクトを(排他的に)保持している間、UIスレッドが待機する必要がないように条件チェックを使用しようとしています。UIアクションを予測し、BGスレッドが「x」秒間実行されるのを遅らせようとしています。また、PriorityQueueを維持して、SQLiteDatabase接続オブジェクトの配布を管理し、UIスレッドが最初にそれを取得できるようにすることもできます。


そして、そのPriorityQueue-リスナー(データベースオブジェクトを取得したい)またはSQLクエリに何を入れますか?
Pijusn 2012

私は優先キューアプローチを使用していませんが、基本的には「呼び出し側」スレッドを使用しています。
Swaroop、2012年

@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ライブラリも含まれています

3つの主要コンポーネントが含まれています:@ 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();
}

Room to a Databaseに複数のN対Nの関係があることはお勧めしません。これは、このアプローチをうまく処理できず、これらの関係の回避策を見つけるために多くのコードを記述する必要があるためです。
AlexPad

3

いくつか問題があったので、なぜ私が間違っているのか理解したと思います。

getWriteableDatabaseを呼び出すclose()ミラーとしてヘルパーを閉じるを含むデータベースラッパークラスを作成open()してから、に移行しましたContentProvider。コードが使用しているので、私は大きな手掛かりだと思うモデルをContentProvider使用していません。一部のインスタンスでは、まだ直接アクセスを行っていました(メインの画面検証クエリなので、getWriteableDatabase / rawQueryモデルに移行しました。SQLiteDatabase.close()getWriteableDatabase

シングルトンを使用していますが、クローズドドキュメントにやや不吉なコメントがあります

開いているデータベースオブジェクトをすべて閉じます

(私の太字)。

そのため、バックグラウンドスレッドを使用してデータベースにアクセスすると、断続的にクラッシュし、フォアグラウンドと同時に実行されました。

そのclose()ため、参照を保持している他のスレッドに関係なく、データベースを強制的に閉じると思います。つまり、close()それ自体は単に一致を取り消すのではなく開いている要求をgetWriteableDatabase強制的に閉じます。コードはシングルスレッドであるため、ほとんどの場合これは問題ではありませんが、マルチスレッドの場合、開いたり閉じたりして非同期になる可能性が常にあります。

SqLiteDatabaseHelperコードインスタンスがカウントされることを説明する他の場所のコメントを読んだ後、閉じる必要があるのは、バックアップコピーを実行したい状況で、すべての接続を強制的に閉じてSqLiteを強制したい場合だけです。滞っている可能性のあるキャッシュされたものをすべて書き出します。つまり、すべてのアプリケーションデータベースアクティビティを停止し、ヘルパーがトラックを失った場合に備えて閉じ、ファイルレベルのアクティビティ(バックアップ/復元)を実行してから、最初からやり直します。

制御された方法で試行して終了することは良い考えのように思えますが、実際にはAndroidがVMを破棄する権利を留保しているため、終了するとキャッシュされた更新が書き込まれないリスクが減少しますが、デバイスがストレスがかかり、カーソルとデータベース(静的メンバーであってはならない)への参照を正しく解放した場合、ヘルパーはとにかくデータベースを閉じます。

だから私の考えは、アプローチは次のとおりです:

シングルトンラッパーから開くには、getWriteableDatabaseを使用します。(派生アプリケーションクラスを使用して、静的のアプリケーションコンテキストを提供し、コンテキストの必要性を解決しました)。

直接closeを呼び出さないでください。

結果のデータベースを、明らかなスコープを持たないオブジェクトに格納しないでください。暗黙のclose()をトリガーするために参照カウントに依存します。

ファイルレベルの処理を行う場合は、すべてのデータベースアクティビティを停止し、適切なトランザクションを作成するという前提で暴走スレッドがある場合に備えてcloseを呼び出し、暴走スレッドが失敗し、閉じたデータベースが少なくとも適切なトランザクションを持つようにします。部分的なトランザクションの潜在的にファイルレベルのコピーよりも。


0

私は応答が遅いことを知っていますが、Androidでsqliteクエリを実行する最良の方法はカスタムコンテンツプロバイダーを使用することです。このようにして、UIはデータベースクラス(SQLiteOpenHelperクラスを拡張するクラス)と分離されます。また、クエリはバックグラウンドスレッド(カーソルローダー)で実行されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.