小白到大神,你须要了解的 sqlite 最佳实践

本文微信公众号「AndroidTraveler」首发。html

背景

本文是对一篇英文文档的翻译,原文请见文末连接。java

<hr/>android

并发数据库访问

假设你实现了本身的 SQLiteOpenHelpergit

public class DatabaseHelper extends SQLiteOpenHelper { ... }

如今你想要在多个线程中对数据库写入数据。github

// 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 中发现下面信息,而且你的其中一个改变不会写入数据库:sql

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

产生这个错误的缘由是由于,每次你建立新的 SQLiteOpenHelper 对象,实际上你建立了新的数据库链接。若是你尝试从不一样的链接同时对数据库写入数据,其中一个会失败。数据库

为了在多线程使用数据库,咱们要确保只使用一个数据库链接。安全

让咱们构造单例类 DatabaseManager,它会持有并返回单个 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

因为咱们只使用了一个数据库链接,Thread1Thread2getDatabase() 方法都会返回同一个 SQLiteDatabase 对象实例。可能发生的场景是 Thread1 关闭了数据库,然而 Thread2 还在使用它。这也就是为何咱们会有 IllegalStateException 的奔溃的缘由。

咱们须要确保没有人正在使用数据库,这个时候咱们才能够关闭它。stackoveflow 上有人推荐永远不要关闭你的 SQLiteDatabase。这会让你看到下面的 logcat 信息。因此我一点也不认为这是一个好的想法。

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

实战例子

一种可能的解决方案是使用计数器跟踪打开/关闭的数据库链接。

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

    public synchronized void closeDatabase() {
        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

每当你须要使用数据库的时候你应该调用 DatabaseManager 类的 openDatabase() 方法。在这个方法里面,咱们有一个计数器,用来代表数据库打开的次数。若是计数为 1,意味着咱们须要建立新的数据库链接,不然,数据库链接已经创建。

对于 closeDatabase() 方法来讲也是同样的。每次咱们调用这个方法的时候,计数器在减小,当减为 0 的时候,咱们关闭数据库链接。

如今你可以使用你的数据库而且确保是线程安全的。

<hr/>

原文:Concurrent database access

因为本人翻译水平有限,若是你有更好的翻译文案,欢迎在 GitHub 提 PR。
这边建了一个仓库,欢迎提 PR 投稿一些好的文章翻译。
并发数据库访问

原文出处:https://www.cnblogs.com/nesger/p/11038248.html

相关文章
相关标签/搜索