使用SQLiteOpenHelper的正確姿勢

      前段時間寫android用到SQLIteOpenHelper時,踩了一些小坑,仔細思考了一下,踩坑的根源在於,我沒有正確的理解SQLiteOpenHelper這個類。那麼,我們一起來看看SQLiteOpenHelper究竟是個什麼東東,以及如何正確地使用SQLiteOpenHelper,希望可以幫助博友們理解,避免踩到了類似的坑。

      本文的將首先介紹下SQLiteOpenHelper的作用,然後引申出一些在使用SQLiteOpenHelper時需要注意的事項,最後,通過一個小demo演示下SQLiteOpenHelper操作數據庫。


SQLiteOpenHelper是什麼


    望文生義,從名字上看來,至少可以得到三個方面的信息。第一,和SQLite數據庫有關。第二,和打開一個數據庫有關。第三,這是一個幫助類。

       當然,這些都是從名字上聯想到的,難免有主觀臆斷之嫌,哈哈。我們來看看官方的說法:A helper class to manage database creation and version management. 所以,官方把SQLiteOpenHelper的作用解釋爲:一個數據庫創建和版本管理的幫助類。

  引起我注意的是,類名起的和解釋不是很切合。既然是管理的幫助類,並且是管理數據庫的創建和版本,那麼爲什麼類名包含"Open"這個單詞呢?爲什麼不叫SQLiteManageHelper,或者更準確的,SQLiteCreationAndUpgradeHelper呢?且聽下文分解。

  我們知道,我們可以通過sql來操縱數據庫,做增刪改查。但是呢,如果是我們從頭開始寫一遍數據庫邏輯的話,其實不只sql操作。一個簡單的流程是這樣的:打開數據庫->sql操作數據庫->關閉數據庫。而打開數據庫,又牽涉到了不少細節。比如:

       1.  這個數據庫是否存在?如果不存在,就要考慮先創建數據庫,創建各種數據表。

       2.  這個數據庫是否需要升級,比如目前的數據庫結構已經不能滿足我們的需求了,我們需要更改數據庫的結構,這個時候就要先升級數據庫,更新數據庫的結構。

       3.  這個數據庫是否之前已經打開?一般來說,我們操作數據庫結束之後,需要關閉數據庫,以節約資源。但是如果我們之前打開的數據庫還要繼續使用,這時候再執行打開操作時,直接返回這個數據庫就行了。

       這些細節實現起來,還是挺繁瑣的,然而這些邏輯都是套路,一招鮮吃遍天,不需要每次都從頭寫一遍。於是,是時候SQLiteOpenHelper出場了。

       SQLiteOpenHelper把打開數據庫的一系列需要考慮到的細節邏輯都封裝了起來,這也是爲什麼包含了Open這個單詞的原因了。因爲SQLiteOpenHelper的主要工作就是把打開數據庫的邏輯幫我們實現了。

       當然,打開數據庫中涉及到的一些業務方面的邏輯,這是需要我們自己去實現的,SQLiteOpenHelper把這些用相應的接口分離了出來。

       onCreate:  創建數據庫時會調用,我們需要在裏面創建我們的數據表。onCreate只會調用一次,如果數據庫已經存在,那麼打開數據庫是不會回調onCreate的。

       onUpgrade:  升級數據庫時會調用,我們可以在這裏面做升級操作。關於升級,還有一點需要注意的,後面會提到。

       onDowngrade: 降級數據庫時會調用,既然存在升級的場景,也可能存在降級的場景,比如我們可能覺得新版的需求沒有舊版的好,想要恢復以前的需求,這時候就需要降級處理了。

        onOpen: 打開數據庫時會調用到。和onCreate有所區別的是,onCreate只在創建數據庫時會調用,而onOpen在每次打開數據庫時都會調用。

        onConfigure:配置數據庫連接。比如設置外鍵約束等。


SQLiteOpenHelper的幾個誤區


       引入正題,我們來看看幾個使用SQLiteOpenHelper可能踩到的坑或者誤區。


       誤區一:創建SQLiteOpenHelper對象的時候,會創建數據庫或者連接數據庫

       不知道用過SQLiteOpenHelper的朋友們有沒有和我一樣,曾經犯過這樣的錯誤,以爲創建SQLiteOpenHelper對象的時候,就會創建相應的數據庫,或者連接已經存在的數據庫。爲什麼會產生這樣的誤解呢?仔細想了想,根源出在構造函數上。

public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
            DatabaseErrorHandler errorHandler)

dbName是數據庫的名字,version是數據庫的版本。既然構造SQLiteOpenHelper的時候,又是傳dbName,又是傳version的,我就想當然的以爲這時候會創建數據庫並且連接上數據庫了。然後打開源碼發現,什麼都沒發生[看不下去]

public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
            DatabaseErrorHandler errorHandler) {
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);

        mContext = context;
        mName = name;
        mFactory = factory;
        mNewVersion = version;
        mErrorHandler = errorHandler;
    }
SQLiteOpenHelper的構造函數裏只是簡單的記錄下傳過來的參數。

      真正創建數據庫的時機是,顯示調用打開數據庫的api纔會執行。也就是getReadableDatabase()和getWriteableDatabase()。這兩個方法都是調用了

private SQLiteDatabase getDatabaseLocked(boolean writable)
而創建數據庫,配置數據庫,升級數據庫,打開數據庫這些邏輯都是在這個方法裏面實現的。

     誤區二:在onCreate, onOpen等回調裏面調用getReadableDatabase或getWriteableDatabase

     因爲前面解釋得很清楚了,getReadableDatabase或getWriteableDatabase是觸發(創建數據庫、打開數據庫、升級數據庫、降級數據庫、配置數據庫)的入口,如果在這些回調裏面,又觸發了getReadableDatabase或getWriteableDatabase的話,你沒看錯,這變成遞歸調用了。

     所以,不能再onCreate, onOpen, onConfigure, onUpgrade, onDowngrade回調裏面調用打開數據庫的方法。


     誤區三:升級數據庫時,只需要考慮從最近的一個版本升級到最新的版本

     這種做法是錯誤的。舉個栗子。

     假設我們的app一共發了三個版本A,B,C,並且版本A的數據庫version是1,版本B的數據庫version升到了2,版本C的數據庫version升到了3。

     版本B裏面的數據庫升級邏輯是

@Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        // 在student表裏面加了一個age字段
    }
     版本C裏面的數據庫升級邏輯是

@Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        // 在student表裏面加了一個hobby字段
    }
     這會導致一個很嚴重的問題。假如用戶從app的版本A升級到版本B再升級到版本C,一切沿着我們的設想,沒有問題。但是如果用戶在收到版本B的更新時,拒絕了更新請求;而收到版本C的更新時,同意了更新。那麼問題來了,student表裏面增加了hobby字段,但少了age字段!因爲用戶拒絕了版本B的更新,意味着沒有執行版本B裏面的數據庫升級邏輯。

     正確的做法是這樣的,在版本C裏面,判斷老版本,如果是1,說明是從版本A升級過來的,那麼就應該添加age, hobby字段;如果是2,說明是從版本B升級過來的,那麼只需要添加hobby字段,因爲版本B裏面已經有age字段,不需要添加了。

@Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        if (oldVersion == 1) {
            // 在student表裏面加了age, hobby字段
        } else if (oldVersion == 2) {
            // 在student表裏面加了hobby字段
        }
    }
     長話短說,升級數據庫,一定要考慮以前的所有版本,對每個版本都進行適配。

     當然,有升級的場景,就有降級的場景。邏輯是類似的,降級的時候,要考慮所有的高版本,對每個高本版都進行適配。


SQLiteOpenHelper的小Demo


     做了一個小demo,實現了幾個小功能:

     1.創建數據庫student_db,創建數據表student

     2.添加一行數據到student表

     3.修改一行數據

     4.刪除一行數據

     5.按名字查詢student表

     6.升級數據庫,給student表添加一個age字段,默認值是20歲


     
下面是demo的下載鏈接,歡迎下載:

Sqlite Demo


總結


    1.SQLiteOpenHelper名字裏包含Open,就是想告訴我們,SQLiteOpenHelper的主要工作就是把打開數據庫的邏輯幫我們實現了。

    2.創建SQLiteOpenHelper對象的時候,不會真正創建數據庫或者連接數據庫。調用getReadableDatabase或getWriteableDatabase纔會觸發該操作。

    3.升級數據庫,一定要考慮以前的所有版本,對每個版本都進行適配。降級的時候,要考慮所有的高版本,對每個高本版都進行適配。

    4.在onCreate, onOpen等回調不能調用getReadableDatabase或getWriteableDatabase,會產生遞歸調用。


感謝您的耐心閱讀,以上如果有錯誤的地方或者理解有失偏頗,請留言指正,謝謝~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章