2012年5月9日 星期三

Android 開發筆記 - 使用 SQLite / SQLiteDatabase / SQLiteOpenHelper

MyDBHelper


SQLite 這幾年來已經成了一種標準用法,無論是嵌入式或是 PC 上的軟體,很容易看的到 SQLite 的蹤跡了。


對 Android 系統架構我還沒那麼熟,粗略知道它有 SQLiteDatabase 和 SQLiteOpenHelper 兩個 class 可供 SQLite 操作使用。網路上翻到的資料,大概要先撰寫一隻 SQLiteOpenHelper,此角色是用來初始化資料庫,包含建 Table 、資料庫升級等對應動作。有些人習慣把資料庫的操作都寫在同一個 class (extends SQLiteOpenHelper)中,由於我經驗尚淺,我不曉得這樣的做法是不是正確的,因為觀看一些物件特性及機制似乎不太恰當。以下用簡易範例當作筆記。


新增 MyDBHelper 並繼承 android.database.sqlite.SQLiteOpenHelper,接著實作 void onCreate(SQLiteDatabase db) 和 void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 後,就算完成 SQLiteOpenHelper 的工作了:


package com.example.study;


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


public class MyDBHelper extends SQLiteOpenHelper {
        final private static int _DB_VERSION = 1;
        final private static String _DB_DATABASE_NAME = "MyDatabases.db";
        public MyDBHelper(Context context) {
                super(context,_DB_DATABASE_NAME,null,_DB_VERSION);
        }
        public MyDBHelper(Context context, String name, CursorFactory factory, int version) {
                super(context, name, factory, version);
                // TODO Auto-generated constructor stub
        }


        @Override
        public void onCreate(SQLiteDatabase db) {
                // TODO Auto-generated method stub
                db.execSQL(
                        "CREATE TABLE MyTable (" +
                                " _ID INTEGER PRIMARY KEY, " +
                                " _DATA VARCHAR(50) NOT NULL, " +
                                " _DATETIME DATETIME NULL " +
                        ")"
                );
        }


        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                // TODO Auto-generated method stub
                db.execSQL("DROP TABLE IF EXISTS MyTable");
                onCreate(db);
        }
}


接著在 MyTestingActivity 中,就可以操作:


package com.example.study;


import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;


public class SQLiteDBTestingActivity extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);

                // Init
                MyDBHelper mHelper = new MyDBHelper(this);
                SQLiteDatabase mDB = null;

                // Insert by raw SQL
                mDB = mHelper.getWritableDatabase();
                mDB.execSQL( "INSERT INTO MyTable (_DATA,_DATETIME) VALUES ('Hello World', datetime('now'))");
                mDB.close();

                // Insert by object method
                mDB = mHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                values.put("_DATA","YoHO");
                values.put("_DATETIME","2012-05-09 20:30:00");
                mDB.insertOrThrow("MyTable",null,values);
                mDB.close();

                // Query by raw SQL
                mDB = mHelper.getWritableDatabase(); // mDB = mHelper.getReadableDatabase();
                Cursor cursor = mDB.rawQuery("SELECT _ID, _DATA, _DATETIME FROM MyTable", null);
                cursor.moveToFirst();
                while(!cursor.isAfterLast()) {
                        Log.e("SQLiteDBTestingActivity","_ID = "+cursor.getInt(0));
                        Log.e("SQLiteDBTestingActivity","_DATA = "+cursor.getString(1));
                        Log.e("SQLiteDBTestingActivity","_DATETIME = "+cursor.getString(2).substring(0, 16));
                        cursor.moveToNext();
                }
                startManagingCursor(cursor);
                cursor.close();
                mDB.close();
        }
}


執行結果:


output


從上述片段程式來看,可以看到一開始 SQLiteOpenHelper 初始化必須傳入一個 Context,並且執行資料庫操作是透過 getWritableDatabase() 或 getReadableDatabase() 而來的,且查詢時會透過 Cursor 甚至 Activity 的 startManagingCursor 等動作,結束時還需做一些 close 的資源回收,因此,個人覺得把這一系列的動作都包進 SQLiteOpenHelper 似乎不太恰當。目前經驗尚淺,或許需要多累積一些實作經驗才會明瞭吧!


此外,可以透過 DDMS 將模擬器內的 SQLiteDB 取出來 (/data/data/com.example.study/databases/MyDatabases.db),並且用 sqlite3 等相仿的 cmd 去操作:


$ sqlite3 MyDatabases.db
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .table
MyTable android_metadata
sqlite> SELECT * FROM MyTable;
1|Hello World|2012-05-09 12:41:07
2|YoHO|2012-05-09 20:30:00
sqlite>


1 則留言:

  1. 請在 onUpgrade() drop 之後加上 onCreate(db);
    否則程式將會在下次執行時出現 exception, 因為 table 已經被 drop 了, 無法再 insert.

    版主回覆:(11/22/2012 05:47:14 AM)


    感謝提醒 :D

    回覆刪除