2012-12-13

【Android】SQLite - 資料庫的應用

SQLite 是 Android 採用的資料庫,因其小巧,所以適合單機使用。

因為 app 要儲存資料在 SQLite 之前,必須先建立 app 用的資料庫檔案,故 Android 提供了 SQLiteOpenHelper 這個類別幫您管理您 app 的資料庫檔案。

首先,建立自己的 app 的 SQLiteOpenHelper:

  1.  
  2. public class DictionaryOpenHelper extends SQLiteOpenHelper
  3. {
  4. // 若您變更了資料庫的 schema, 就必須增加 DATABASE_VERSION 的值.
  5. public static final int DATABASE_VERSION = 1;
  6. public static final String DATABASE_NAME = "whalintest.db";
  7. SQLite(Context context)
  8. {
  9. super(context, DATABASE_NAME, null, DATABASE_VERSION);
  10. }
  11.  
  12. @Override
  13. public void onCreate(SQLiteDatabase db)
  14. {
  15. db.execSQL("CREATE TABLE table1 (field1 VARCHAR(5), field2 VARCHAR(9));");
  16. }
  17.  
  18. @Override
  19. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
  20. {
  21. // This database is only a cache for online data, so its upgrade policy is
  22. // to simply to discard the data and start over
  23. db.execSQL("DROP TABLE IF EXISTS table1");
  24. onCreate(db);
  25. }
  26. @Override
  27. public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)
  28. {
  29. onUpgrade(db, oldVersion, newVersion);
  30. }
  31. }
  32.  


在這類別中,我們 Override 了 3 個 method -- onCreateonUpgrade、和 onDowngrade,onCreate 就是建立您的 app 要使用的資料庫;而當您的 app 增加了新的功能導致資料庫結構也要跟著調整時,要調整的程式碼就寫在 onUpgrade,並且 DATABASE_VERSION 這個變數的值,要比前一版的數字大,Android 在偵測到版本變大時,就會進行資料庫版本升級的動作,在本例是相當偷懶的 -- 直接砍掉重練!! :-P

相對地,Android 偵測到 DATABASE_VERSION 數字較原有的值小時,就會執行 onDowngrade。

然後在程式中進行資料的存取:
  1.  
  2. @SuppressLint("NewApi")
  3. public class MainActivity extends Activity
  4. {
  5. Button button1;
  6. public static final boolean DEVELOPER_MODE = BuildConfig.DEBUG;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState)
  9. {
  10. if(DEVELOPER_MODE)
  11. {
  12. StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
  13. .detectDiskWrites()
  14. .detectNetwork() // or .detectAll() for all detectable problems
  15. .penaltyLog()
  16. .build());
  17. StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()
  18. .detectLeakedClosableObjects()
  19. .penaltyLog()
  20. .penaltyDeath()
  21. .build());
  22. }
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_main);
  25. button1 = (Button) findViewById(R.id.button1);
  26. }
  27.  
  28. public void onButton1Click(View v)
  29. {
  30. Thread thread = new Thread()
  31. {
  32. @Override
  33. public void run()
  34. {
  35. super.run();
  36. SQLite mDbHelper = new SQLite(getBaseContext());
  37. // 以寫入模式開啟資料庫
  38. SQLiteDatabase db = mDbHelper.getWritableDatabase();
  39.  
  40. // 將要存入的欄位-值 以 ContentValues 做準備
  41. ContentValues values = new ContentValues();
  42. values.put("field1", "ann");
  43. values.put("field2", "huang");
  44. // 以 insert 存入一筆記錄
  45. long newRowId;
  46. newRowId = db.insert("table1", null, values);
  47. // 記得使用完畢後要關閉
  48. db.close();
  49. mDbHelper.close();
  50. // --------------------
  51. mDbHelper = new SQLite(getBaseContext());
  52. // 以讀取模式開啟資料庫
  53. db = mDbHelper.getReadableDatabase();
  54.  
  55. // db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
  56. // 不用到的功能或選項就填入 null
  57. Cursor rs = db.query("table1", null, "field1=\"ann\" ", null, null, null, null);
  58.  
  59. // 或是您也可以用 rawQuery, 輸入完整的 SQL 語法
  60. // 這在查詢 2 個以上的資料表時很好用
  61. // db.rawQuery("SELECT a.field1, b.field3, b.field4 FROM table1 a, table2 b \n" +
  62. // " WHERE a.field1=b.field2" , null);
  63. // 記得使用完畢後要關閉
  64. rs.close();
  65. db.close();
  66. mDbHelper.close();
  67. }
  68. };
  69. thread.start();
  70. }
  71. }
  72.  


db.query() 中 columns 若為 null 則表全部的欄位,等於一般的 SELECT * FROMselection selectionArgs 就是 SQL 指令中的 WHERE 述句,selection 是欄位,selectionArgs 是要符合的條件,偷懶的寫法是 -- 直接在 selection 填入全部的 WHERE 條件,selectionArgs 則為 null。

另外,還要注意每當下了 db.query() 指令後,就等於開啟了一個查詢結果資料集(query data set) ,要記得將它關閉。如下:
  1.  
  2. // 這裡開啟了一個 query data set
  3. // 我們以 Cursor rs 去 reference 指向這個 data set
  4. Cursor rs = db.query("table1", null, "field1=\"ann\" ", null, null, null, null);
  5. rs.close();
  6.  
  7. // 重新 query,又開啟了一個 query data set
  8. // 再將 rs 指向這個 data set
  9. rs = db.query("table1", null, "field1=\"bryan\" ", null, null, null, null);
  10. // 注意這裡我沒有執行 rs.close();
  11.  
  12. // 第三次 query,又開啟了一個 query data set
  13. // 再將 rs 指向這個 data set
  14. rs = db.query("table1", null, "field1=\"john\" ", null, null, null, null);
  15. rs.close();
  16.  
  17. // 因為第二次的 query 沒有 close(),所以它一直處在開啟的狀態
  18. // 而我們又將 rs 指向了第三次的 query data set
  19. // 如果您有啟動 StrictMode 抓 bug,就會被 StrictMode 偵測到沒有關閉 query 的警告了
  20.  



要連線資料庫查看資料的儲存狀況:

1. 先開啟一個 Android 模擬器,您會看到模擬器視窗左上角有個數字,我的是 5554

2. 開啟一個 "命令提示字元"視窗(DOS視窗) 以 cd 指令切換到您安裝的 sdk 資料夾的 platform-tools 下,舉例來說,我的 SDK 安裝在 D:\,則為

cd \ android\android-sdk\platform-tools

3. 下達連線模擬器指令

adb -s emulator-5554 shell

4. 連線您應用程式所使用資料庫,則下指令

sqlite3   /data/data/您的 package name/databases/資料庫名稱

這時您會看到多列文字顯示連線的過程


同時它也提示您, 下指令 .help 可取得輔助說明,並且下達的 SQL 指令要以 分號 做結尾

試著下 .dump,倒出資料庫所有資料


沒錯,我的確先前試寫時,執行了 db.insert(),在 table1 這個資料表存了三筆一樣的記錄。

退出 sqlite3 指令為 .exit,或按 CTRL+D

要記得舉凡 "磁碟機(含記憶卡)" 的存取,網線連線...不論管您在執行這些動作的程式碼多簡單,這些 Android 都認為是屬於耗時的工作,為有良好品質的 app,請將資料庫的存取視為耗時工作,將這段指令以 Thread 執行,並請用 StrictMode 來檢驗您的 app 指令有哪些是違反 StrictMode 的政策。