SQLite 是一種輕量化的關聯式資料庫,適合用來儲存複雜且重複的結構化資料,如果您熟悉 SQL 資料庫,那麼使用 SQLite 必定能駕輕就熟,使用上大同小異。 目前 Android 已經內建 SQLite ,無需安裝任何套件,也無需任何權限就能使用,且每個 APP 的 SQLite 各自獨立無法互相存取,當 APP 解除安裝後,資料也會一併刪除

雖然大部分 APP 的資料都是透過 API 取得,感覺好像不需要在手機裡面放一個資料庫。 但這還是要看您的 APP 功能需求,如果需要在沒有網路的狀況下也能使用的話,那就有很大機會使用到 SQLite。 又或者有大量不太會異動的資料,這樣也能用 SQLite 當作快取使用,可減少頻繁的與伺服器連線。

由於 SQLite API 存在一些缺點,因此 Google 強烈建議使用 Room 持續性資料庫做為抽象層,透過 Room 來存取 SQLite 資料庫中的資訊。

建立 SQLite Helper

您可以使用 SQLiteOpenHelper 類別協助管理您的資料庫,此類別只會在必要時開啟資料庫連線,也就是當您呼叫 getWritableDatabase() 或 getReadableDatabase() 時才會建立連線,並且執行建立和更新資料庫等工作。 而 SQLiteOpenHelper 建立的資料庫檔案,會儲存在 Internal Storage 的 /data/data/[Package Name]/databases 目錄中。

定義資料表結構

首先必需先定義資料表結構,後續操作會很常用到這個類別。

                
                    public final class UserReaderContract {
                        private UserReaderContract() {}

                        public static class UserEntry implements BaseColumns {
                            public static final String TABLE_NAME = "user";
                            public static final String COLUMN_NAME_ID = "id";
                            public static final String COLUMN_NAME_NAME = "name";
                            public static final String COLUMN_NAME_ACCOUNT = "account";
                        }
                    }
                
            

建立資料庫

                
                    public class DatabaseHelper extends SQLiteOpenHelper {
                        public static final int DATABASE_VERSION = 1;
                        public static final String DATABASE_NAME = "test.sqlite";

                        private static final String SQL_CREATE_ENTRIES =
                            "CREATE TABLE " + UserEntry.TABLE_NAME + " (" +
                            UserEntry._ID + " INTEGER PRIMARY KEY," +
                            UserEntry.COLUMN_NAME_ID + " TEXT," +
                            UserEntry.COLUMN_NAME_NAME + " TEXT," +
                            UserEntry.COLUMN_NAME_ACCOUNT + " TEXT)";

                        private static final String SQL_DELETE_ENTRIES =
                            "DROP TABLE IF EXISTS " + UserEntry.TABLE_NAME;

                        public DatabaseHelper(Context context) {
                            super(context, DATABASE_NAME, null, DATABASE_VERSION);
                        }

                        @Override
                        public void onCreate(SQLiteDatabase database) {
                            database.execSQL(SQL_CREATE_ENTRIES);
                        }

                        @Override
                        public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
                            database.execSQL(SQL_DELETE_ENTRIES);
                            onCreate(database);
                        }

                        @Override
                        public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion) {
                            onUpgrade(database, oldVersion, newVersion);
                        }
                    }
                
            
  • 行 2:當 DATABASE_VERSION 值不一樣時,將會觸發 onUpgrade() 升級或 onDowngrade() 降級。假設使用者之前已在手機安裝 APP,且新版 APP 使用的 SQLite 結構也有異動,此時就需要修改 DATABASE_VERSION 的數值,否則程式會認為在目錄中的 SQLite 檔案,與現在程式使用的資料庫結構相同,執行時便會出錯。
  • 行 3:資料庫名稱,也就是實際存放在目錄中的檔案名稱。
  • 行 5 ~ 10:建立資料表的 SQL 與法。
  • 行 12 ~ 13:刪除資料表的 SQL 與法。
  • 行 20 ~ 22:當目錄中沒有 SQLite 檔案時,系統會自動呼叫。如果有初始資料,也應該在這裡寫入。
  • 行 25 ~ 28:當資料庫版本升級時,系統會自動呼叫。通常是執行刪除舊資料表,再呼叫 onCreate() 添加新版資料表。
  • 行 31 ~ 33:當資料庫版本降級時,系統會自動呼叫。通常是執行刪除舊資料表,再呼叫 onCreate() 添加新版資料表。onDowngrade() 不一定需要複寫,如果沒有複寫的話,預設會拋出 SQLiteException 例外。

資料庫操作

以下是 SQLite 資料庫常用操作程式碼,提供大家參考。

新增資料

                
                    DatabaseHelper dbHelper = new DatabaseHelper(this);
                    SQLiteDatabase database = dbHelper.getWritableDatabase();
                    ContentValues values = new ContentValues();
                    values.put(UserEntry.COLUMN_NAME_ID, "0000001");
                    values.put(UserEntry.COLUMN_NAME_NAME, "測試姓名");
                    values.put(UserEntry.COLUMN_NAME_ACCOUNT, "ID0000001");
                    long newRowId = database.insert(UserEntry.TABLE_NAME, null, values);
                
            

讀取資料

                
                    DatabaseHelper dbHelper = new DatabaseHelper(this);
                    SQLiteDatabase database = dbHelper.getReadableDatabase();
                    String[] projection = {
                        BaseColumns._ID,
                        UserEntry.COLUMN_NAME_ID,
                        UserEntry.COLUMN_NAME_NAME,
                        UserEntry.COLUMN_NAME_ACCOUNT
                    };
                    String selection = UserEntry.COLUMN_NAME_ID + " != ?";
                    String[] selectionArgs = {"0"};
                    String sortOrder = UserEntry.COLUMN_NAME_ID + " ASC";

                    // 查詢
                    Cursor cursor = database.query(
                        UserEntry.TABLE_NAME,      // 要查詢的資料表
                        projection,                // 要查詢的欄位 (使用 null 表示全部欄位)
                        selection,                 // 過濾欄位 (相當於 WHERE 後面的條件)
                        selectionArgs,             // 過濾欄位的資料 (相當於 WHERE 後面條件的資料)
                        null,                      // 分組 (相當於 SQL 中 GROUP BY 後面的語法)
                        null,                      // (相當於 SQL 中 HAVING 後面的語法)
                        sortOrder                  // 排序 (相當於 ORDER BY 後面的語法)
                    );

                    // 將資料放入 List
                    List<Map<String, String>> itemList = new ArrayList<>();
                    while (cursor.moveToNext()) {
                        String id = cursor.getString(cursor.getColumnIndexOrThrow(UserEntry.COLUMN_NAME_ID));
                        String account = cursor.getString(cursor.getColumnIndexOrThrow(UserEntry.COLUMN_NAME_ACCOUNT));
                        String name = cursor.getString(cursor.getColumnIndexOrThrow(UserEntry.COLUMN_NAME_NAME));

                        // 加入至 List
                        Map<String, String> row = new HashMap<>();
                        row.put(UserEntry.COLUMN_NAME_ID, id);
                        row.put(UserEntry.COLUMN_NAME_ACCOUNT, account);
                        row.put(UserEntry.COLUMN_NAME_NAME, name);
                        itemList.add(row);
                    }
                    cursor.close();
                
            
  • 行 14:查詢結果的全部資料都會放在 cursor 變數中。
  • 行 27 ~ 37:這邊只是將 cursor 轉換成常用的集合物件。

刪除資料

                
                    DatabaseHelper dbHelper = new DatabaseHelper(this);
                    SQLiteDatabase database = dbHelper.getWritableDatabase();
                    String selection = UserEntry.COLUMN_NAME_ID + " = ?";
                    String[] selectionArgs = {"0000001"};

                    // 回傳刪除的資料筆數
                    int count = database.delete(
                        UserEntry.TABLE_NAME,      // 要刪除資料所在的資料表
                        selection,                 // 篩選欄位 (相當於 WHERE 後面的條件)
                        selectionArgs              // 篩選欄位的資料 (相當於 WHERE 後面條件的資料)
                    );
                
            

更新資料

                
                    DatabaseHelper dbHelper = new DatabaseHelper(this);
                    SQLiteDatabase database = dbHelper.getWritableDatabase();

                    // 更新資料
                    ContentValues values = new ContentValues();
                    values.put(UserEntry.COLUMN_NAME_NAME, "測試姓名2");

                    // 篩選欄位
                    String selection = UserEntry.COLUMN_NAME_ID + " = ?";
                    String[] selectionArgs = { "0000002" };

                    // 回傳更新的資料筆數
                    int count = database.update(
                        UserEntry.TABLE_NAME,      // 要更新的資料表
                        values,                    // 更新資料
                        selection,                 // 篩選欄位 (相當於 WHERE 後面的條件)
                        selectionArgs              // 篩選欄位的資料 (相當於 WHERE 後面條件的資料)
                    );
                
            

執行 SQL 語法

                
                    DatabaseHelper dbHelper = new DatabaseHelper(this);

                    // 更新資料
                    SQLiteDatabase databaseWritable = dbHelper.getWritableDatabase();
                    String sqlUpdate = "UPDATE user SET name = ? WHERE id = ?";
                    databaseWritable.execSQL(sqlUpdate, new String[]{"測試姓名2", "0000002"});

                    // 查詢資料
                    SQLiteDatabase databaseReadable = dbHelper.getReadableDatabase();
                    String sqlSelect = "SELECT id, name, account FROM user WHERE id = ?";
                    Cursor cursor = databaseReadable.rawQuery(sqlSelect, new String[]{"0000002"});
                
            
  • 行 6:如果需要回傳值,可以用 execSQL()。
  • 行 11:如果不需要回傳值,可以用 rawQuery()。

關閉連線

當資料庫處於關閉狀態時,呼叫 getWritableDatabase() 及 getReadableDatabase() 將會耗費較高的系統效能,建議不要頻繁的開關連線,盡量在資料庫存取期間保持連線開啟,最好的做法就是在 onDestroy() 關閉資料庫連線。

                
                    @Override
                    protected void onDestroy() {
                        dbHelper.close();
                        super.onDestroy();
                    }
                
            

使用預先準備的 SQLite 檔案

如果使用的資料庫具有大量的初始資料,您可能會希望使用自己預先準備好的 SQLite 檔案,而不是像上述範例必需在程式碼中建立資料表,再一筆一筆將資料寫入資料庫。Google 官方文件並沒有說明如何使用預先準備的 SQLite 檔,以下範例僅供參考,雖然能正常運作,但不確定是否為好的做法。

做法是先將 SQLite 檔案放到 assets 目錄,之後在程式碼中將檔案複製到 APP 專屬目錄,最後就能依照上述介紹的方法存取資料。以下程式碼同樣支援版本功能,提供大家參考。

            
                public class DatabaseHelper extends SQLiteOpenHelper {
                    public static final int DATABASE_VERSION = 1;
                    public static final String DATABASE_DIR = "/data/data/[Package Name]/databases/";
                    public static final String DATABASE_NAME = "test.sqlite";
                    public static final String DATABASE_PATH = DATABASE_DIR + DATABASE_NAME;
                    public static final String DATABASE_OLD_PATH = DATABASE_DIR + "old_" + DATABASE_NAME;

                    private final Context context;
                    private boolean createDatabase = false;
                    private boolean upgradeDatabase = false;

                    public DatabaseHelper(Context context) {
                        super(context, DATABASE_NAME, null, DATABASE_VERSION);
                        this.context = context;
                    }

                    public void initializeDataBase() {
                        getWritableDatabase();
                        try {
                            if (createDatabase) {
                                copyDatabase();
                            } else if (upgradeDatabase) {
                                copyFile(new FileInputStream(DATABASE_PATH), new FileOutputStream(DATABASE_OLD_PATH));
                                copyDatabase();
                            }
                        } catch (IOException ignored) {
                        }
                    }

                    private void copyDatabase() throws IOException {
                        close();
                        InputStream inputStream = context.getAssets().open(DATABASE_NAME);
                        OutputStream outputStream = new FileOutputStream(DATABASE_PATH);
                        copyFile(inputStream, outputStream);
                    }

                    private void copyFile(InputStream inputStream, OutputStream outputStream) throws IOException {
                        byte[] buffer = new byte[1024];
                        int length;
                        try {
                            while ((length = inputStream.read(buffer)) > 0) {
                                outputStream.write(buffer, 0, length);
                            }
                        } finally {
                            try {
                                if (outputStream != null) {
                                    try {
                                        outputStream.flush();
                                    } finally {
                                        outputStream.close();
                                    }
                                }
                            } finally {
                                if (inputStream != null) {
                                    inputStream.close();
                                }
                            }
                        }
                    }

                    @Override
                    public void onCreate(SQLiteDatabase database) {
                        createDatabase = true;
                    }

                    @Override
                    public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
                        upgradeDatabase = true;
                    }
                }
            
        
  • 行 3:記得改成您的 Package Name。
            
                // 使用前需要先初始化
                DatabaseHelper dbHelper = new DatabaseHelper(this);
                dbHelper.initializeDataBase();

                // 後續就可以使用上一章節的範例存取資料庫
                SQLiteDatabase database = dbHelper.getReadableDatabase();