在Android中一共提供了5種數據存儲方式,分別為:
(1)Files:通過FileInputStream和FileOutputStream對文件進行操作。具體使用方法可以參閱博文《Android學習筆記34:使用文件存儲數據》。
(2)Shared Preferences:常用來存儲鍵值對形式的數據,對系統配置信息進行保存。具體使用方法可以參閱博文《Android學習筆記35:使用Shared Preferences方式存儲數據》。
(3)Content Providers:數據共享,用於應用程序之間數據的訪問。
(4)SQLite:Android自帶的輕量級關系型數據庫,支持SQL語言,用來存儲大量的數據,並且能夠對數據進行使用、更新、維護等操作。
(5)Network:通過網絡來存儲和獲取數據。
本篇博文介紹第四種方式,通過Android自帶的SQLite數據庫存儲數據。
1.SQLite簡介
SQLite是一款開源的、嵌入式關系型數據庫,第一個版本Alpha發布於2000年。SQLite在便攜性、易用性、緊湊性、高效性和可靠性方面有着突出的表現。
SQLite和C/S模式的數據庫軟件不同,它是一款嵌入式數據庫,沒有獨立運行的進程,與所服務的應用程序在應用程序進程空間內共生共存。它的代碼與應用程序代碼也是在一起的,或者說嵌入其中,作為托管它的程序的一部分。因此不存在數據庫的客戶端和服務器,使用SQLite一般只需要帶上它的一個動態庫,就可以享受它的全部功能。
數據庫服務器在程序中的好處是不需要網絡配置或管理。將數據庫客戶端與服務器運行在同一個進程中,可以省去不少的操作及麻煩:不用擔心防火牆或者地址解析;不用浪費時間管理復雜的授權和權限;可以減少網絡調用相關的消耗;可以簡化數據庫管理並使程序更容易部署。
SQLite數據庫通過數據庫級上的獨占性和共享鎖來實現獨立事務處理。這意味着多個進程可以在同一時間從同一數據庫讀取數據,但是只有一個可以寫入數據。在某個進程向數據庫執行寫操作之前,必須獲得獨占鎖定。在發出獨占鎖定后,其他的讀寫操作將不會再發生。
此外,SQLite數據庫中的所有信息(比如表、視圖、觸發器等)都包含在一個文件內,方便管理和維護。SQLite數據庫還支持大部分操作系統,除電腦上使用的操作系統之外,很多手機上使用的操作系統同樣可以運行。同時,SQLite數據庫還提供了多語言的編程接口,供開發者使用。
2.SQL基本命令
SQL是與關系型數據庫通信的唯一方式。它專注於信息處理,是為構建、讀取、寫入、排序、過濾、映射、分組、聚集和通常的管理信息而設計的聲明式語言。
在講解SQL基本命令之前,有必要先了解一下SQLite所支持的數據類型都有哪些。
2.1 SQLite支持的數據類型
SQLite采用動態數據存儲類型,會根據存入的值自動進行判斷。SQLite支持以下5種數據類型:
(1)NULL:空值
(2)INTEGER:帶符號的整型
(3)REAL:浮點型
(4)TEXT:字符串文本
(5)BLOB:二進制對象
2.2 SQL基本命令
表是探索SQLite中SQL的起點,也是關系型數據庫中信息的標准單位,所有的操作都是以表為中心的。那么如何使用SQL命令創建一張表呢?
2.2.1創建表
表是由行和列組成的,列稱為字段,行稱為記錄。
使用CREATE命令可以創建表,CREATE命令的一般格式為:
CREATE [TEMP/TEMPORARY] TABLE table_name (column_definitions [, constraints]);
其中,[]中的內容是可選的,用TEMP或TEMPORARY關鍵字聲明的表是臨時表,這種表只存活於當前會話,一旦連接斷開,就會被自動銷毀。如果沒有明確指出創建的表是臨時表,則創建的是基本表,將會在數據庫中持久存在,這也是數據庫中最常見的表。
CREATE TABLE命令至少需要一個表名和一個字段名,上述命令中的table_name表示表名,表名必須與其他標識符不同。column_definitions由用逗號分隔的字段列表組成,每個字段定義包括一個名稱、一個域(類型)和一個逗號分隔的字段約束。其中,域是指存儲在該列的信息的類型,約束用來控制什么樣的值可以存儲在表中或特定的字段中。
一條創建表的命令示例如下:
1 CREATE TABLE tab_student (studentId INTEGER PRIMARY KEY AUTOINCREMENT, 2 studentName VARCHAR(20), 3 studentAge INTEGER);
如上,我們創建了一個名為tab_student的表,該表包含3個字段:studentId、 studentName和studentAge,其數據類型分別為:INTEGER、VARCHAR和INTEGER。
此外,通過使用關鍵字PRIMARY KEY,我們指定了字段studentId所在的列是主鍵。主鍵確保了每一行記錄在某種方式上與表中的其他行記錄是不同的(唯一的),進而確保了表中的所有字段都是可尋址的。
SQLite為主鍵提供自增長功能,當定義字段類型為INTEGER PRIMARY KEY時,SQLite將為該字段創建默認值,該默認值確保整數值是唯一的。SQLite使用64-bit單符號整數主鍵,因此,該字段的最大值是9,223,372,036,854,775,807。當達到最大值時,SQLite會自動搜索該字段還未使用的值,並作為要插入的值。從表中刪除記錄時,rowid可能被回收並在后面的插入中使用。因此,新創建的rowid不一定是按照嚴格順序增長的。如果想要SQLite使用唯一的自動主鍵值,而不是填補空白,可以在主鍵定義INTEGER PRIMARY KEY中加入關鍵字AUTOINCREMENT。AUTOINCREMENT關鍵字阻止rowid回收,它將為新插入的記錄產生新的(不是回收的)rowid。
2.2.2插入記錄
使用INSERT命令可以一次插入一條記錄,INSERT命令的一般格式為:
INSERT INTO tab_name (column_list) VALUES (value_list);
其中,tab_name指明將數據插入到哪個表中,column_list是用逗號分隔的字段名稱,這些字段必須是表中存在的,value_list是用逗號分隔的值列表,這些值是與column_list中的字段一一對應的。
比如,向剛才創建的tab_student表中插入一條記錄,便可以使用如下的語句完成:
INSERT INTO tab_student (studentId, studentName, studentAge) VALUES (1, “jack”, 23);
通過以上的語句,便插入了一條studentName=”jack”, studentAge=”23”的記錄,該記錄的主鍵為studentId=1。
2.2.3更新記錄
使用UPDATE命令可以更新表中的記錄,該命令可以修改一個表中一行或者多行中的一個或多個字段。UPDATE命令的一般格式為:
UPDATE tab_name SET update_list WHERE predicate;
其中,update_list是一個或多個字段賦值的列表,字段賦值的格式為column_name=value。WHERE子句使用斷言識別要修改的行,然后將更新列應用到這些行。
比如,要更新剛才插入到tab_student表中的記錄,便可以使用如下的語句完成:
UPDATE tab_student SET studentName=”tom”, studentAge=”25” WHERE studentId=1;
通過以上的語句,便可以將剛才插入的主鍵為studentId=1的記錄更新為studentName=”tom”, studentAge=”25”了。
2.2.4刪除記錄
使用DELETE命令可以刪除表中的記錄,DELETE命令的一般格式為:
DELETE FROM table_name WHERE predicate;
其中,table_name指明所要刪除的記錄位於哪個表中。和UPDATE命令一樣,WHERE子句使用斷言識別要刪除的行。
比如,要刪除剛才插入的記錄,便可以使用如下的語句完成:
DELETE FROM tab_student WHERE studentId=1;
2.2.5查詢記錄
SELECT命令是查詢數據庫的唯一命令。SELECT命令也是SQL命令中最大、最復雜的命令。
SELECT命令的通用形式如下:
SELECT [distinct] heading
FROM tables
WHERE predicate
GROUP BY columns
HAVING predicate
ORDER BY columns
LIMIT count,offset;
其中,每個關鍵字(如FROM、WHERE、HAVING等)都是一個單獨的子句,每個子句由關鍵字和跟隨的參數構成。GROUP BY和HAVING一起工作可以對GROUP BY進行約束。ORDER BY使記錄集在返回之前按一個或多個字段的值進行排序,可以指定排序方式為ASC(默認的升序)或DESC(降序)。此外,還可以使用LIMIT限定結果集的大小和范圍,count指定返回記錄的最大數量,offset指定偏移的記錄數。
在上述的SELECT命令通用形式中,除了SELECT之外,所有的子句都是可選的。目前最常用的SELECT命令由三個子句組成:SELECT、FROM、WHERE,其基本語法形式如下:
SELECT heading FROM tables WHERE predicate;
比如,要查詢剛才插入的記錄,便可以使用如下的語句完成:
SELECT studentId, studentName, studentAge FROM tab_student WHERE studentId=1;
至此,我們介紹了SQL中最基本和最常用的CREATE、INSERT、UPDATE、DELETE和SELECT命令。當然了,這里只是對其進行了簡單的介紹,有關SQLite中SQL命令的詳細使用方法,可以參閱《SQLite權威指南》一書的第三章和第四章。
3.數據庫操作輔助類SQLiteOpenHelper
Android提供了一個重要的類SQLiteOpenHelper,用於輔助用戶對SQLite數據庫進行操作。
SQLiteOpenHelper的構造函數原型如下:
public SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version);
其中,參數context表示應用程序運行的環境,包含應用程序所需的共享資源。參數name表示Android的數據庫名字。參數factory是SQLiteDatabase.CursorFactory類對象,用於存儲查詢Android SQLite數據庫的結果集。參數version表示應用程序所用的數據庫的版本,該版本並非SQLite的真正版本,而是指定應用程序中的SQLite數據庫的版本,當該版本號發生變化時,將會觸發SQLiteOpenHelper類中的onUpgrade()或onDowngrade()方法。
SQLiteOpenHelper類的所有方法如圖1所示。
圖1 SQLiteOpenHelper類的方法
其中,close()方法用於關閉SQLiteOpenHelper對象中的SQLite數據庫;getReadableDatabase()方法和getWriteableDatabase()方法類似,getReadableDatabase()方法以只讀狀態打開SQLiteOpenHelper對象中指定的SQLite數據庫,任何想要修改數據庫的操作都是不允許的;getWriteableDatabase()方法也是打開數據庫,但是允許數據庫正常的讀/寫操作;在一個不存在的數據庫上調用任何方法時,都會隱式的調用SQLiteOpenHelper對象的onCreate()方法;當應用程序第一次訪問數據庫時,則會調用onOpen()方法,但是,如果版本號發生了變化的話,則會調用onUpgrade()或onDowngrade()方法。
4.數據庫類SQLiteDatabase
SQLiteDatabase類用來完成對數據庫的操作任務,比如表的選擇、插入、更新和刪除語句等。
SQLiteDatabase類中常用的用於執行SQL語句的方法有以下一些。
(1)execSQL()方法:
public void execSQL (String sql);
public void execSQL (String sql, Object[] bindArgs);
(2)query()方法:
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having,String orderBy, String limit);
public Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal);
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having,String orderBy);
public Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
(3)queryWithFactory()方法:
public Cursor queryWithFactory (SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[]columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit,CancellationSignal cancellationSignal);
public Cursor queryWithFactory (SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[]columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
(4)rawQuery()方法:
public Cursor rawQuery (String sql, String[] selectionArgs, CancellationSignal cancellationSignal);
public Cursor rawQuery (String sql, String[] selectionArgs);
(5)rawQueryWithFactory()方法:
public Cursor rawQueryWithFactory (SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs,String editTable);
public Cursor rawQueryWithFactory (SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs,String editTable, CancellationSignal cancellationSignal);
其中,execSQL()方法都有一個參數sql,這個參數是一個SQL語句。第二個參數bindArgs接收一個數組,數組中的每個成員捆綁了一個查詢。execSQL()方法用於運行那些沒有返回值的查詢語句,比如創建、插入、更新和修改表。
query()方法和queryWithFactory()方法是在數據庫中運行一些輕量級的單查詢語句,參數包括table、columns、groupBy、having、orderBy、limit等SQL語句關鍵字。這些方法允許將SQL語句傳遞給相關方法,而不必直接使用SQL語句。
rawQuery()方法和rawQueryWithFactory()方法也都有一個參數sql,用於執行SQL查詢語句,返回值是Cursor對象。這兩個方法都有一個版本能夠接收一個字符串數組selectionArgs作為參數,通過這個參數,SQLiteDatabase對象將把捆綁的SQL語句中的問號(?)用這個數組中的值代替,並按照一一對應的位置關系進行取代。
SQLiteDatabase類提供了大約50個方法,除此之外還有一些用於打開數據庫的方法(如openDatabase()、openOrCreateDatabase()等),用於管理SQLite事務的方法(如beginTransaction()、endTransaction()等),用於測試數據庫是否被鎖住的方法(如isDbLockedByCurrentThread()、isDbLockedByOtherThread()等),以及獲取數據庫基本信息的方法(如getMaximumSiza()、getVersion()等)。這里就不一一介紹了,具體可以參閱SQLiteDatabase類的API幫助文檔。
5.游標類Cursor
在Android中,查詢數據是通過Cursor類來實現的,當我們使用SQLiteDatabase.query()或SQLiteDatabase.rawQuery()方法時,會得到一個Cursor對象,Cursor指向的就是每一條記錄,它提供了很多有關查詢的方法,如圖2所示。
圖2 Cursor類的常用方法
6.封裝接口
有了以上的基礎,我們便可以按照MVC的架構,封裝一個接口層,在該接口層中實現對SQLite數據庫的具體操作。
以下分別以添加數據、更新數據、查詢數據為例講解其具體的實現方法。在實現這些方法之前,我們首先需要創建一張表。這里我創建了一個名為MySQLiteOpenHelper的類,讓它繼承自SQLiteOpenHelper類,並實現了SQLiteOpenHelper類的onCreate()方法,在該方法里實現創建一張表的操作,具體源代碼如下:
1 /* 2 * Function : 創建表 3 * Author : 博客園-依舊淡然 4 */ 5 public void onCreate(SQLiteDatabase db) { 6 db.execSQL("CREATE TABLE tab_student (studentId INTEGER PRIMARY KEY AUTOINCREMENT, " +
"studentName VARCHER(20), " +
"studentAge INTEGER)"); 7 }
通過以上的代碼,我們創建了一張名為“tab_student”的表,並在該表中創建了三個字段,分別為:studentId、studentName和studentAge。並且指定了studentId字段作為該表的主鍵。
6.1添加數據
添加數據可以使用SQLiteDatabase.execSQL(String sql, Object[] bindArgs)方法來實現,具體如下:
1 /* 2 * Function : 添加數據 3 * Author : 博客園-依舊淡然 4 */ 5 public void addStudentInfo(Student student) { 6 db = mySQLiteOpenHelper.getWritableDatabase(); 7 db.execSQL("INSERT INTO tab_student (studentId, studentName, studentAge) values (?, ?, ?)", 8 new Object[] {student.getStudentId(), student.getStudentName(), student.getStudentAge()}); 9 }
其中,通過第二個參數bindArgs,使SQL語句中的問號(?)與這個數組中的值形成一一對應關系,從而將值寫入到“tab_student”表中的對應字段中。
6.2更新數據
更新數據的方法與添加數據的方法大致相同,具體如下:
1 /* 2 * Function : 更新數據 3 * Author : 博客園-依舊淡然 4 */ 5 public void updateStudentInfo(Student student) { 6 db = mySQLiteOpenHelper.getWritableDatabase(); 7 db.execSQL("UPDATE tab_student SET studentName = ?, studentAge = ? WHERE studentId = ?", new Object[] {student.getStudentName(), student.getStudentAge(), student.getStudentId()}); 8 }
6.3查詢數據
查詢數據時,因為需要返回查詢的結果,所以需要使用SQLiteDatabase.rawQuery()方法將查詢的結果返回,具體如下:
1 /* 2 * Function : 查詢數據 3 * Author : 博客園-依舊淡然 4 */ 5 public Student findStudentInfo(int id) { 6 db = mySQLiteOpenHelper.getWritableDatabase(); 7 String sql = "SELECT studentId, studentName, studentAge FROM tab_student WHERE studentId = ?"; 8 Cursor cursor = db.rawQuery(sql, new String[] {String.valueOf(id)}); 9 if(cursor.moveToNext()) { 10 return new Student(cursor.getInt(cursor.getColumnIndex("studentId")),
cursor.getString(cursor.getColumnIndex("studentName")), 11 cursor.getInt(cursor.getColumnIndex("studentAge"))); 12 } 13 return null; 14 }
可以看出,通過使用SQLiteDatabase.rawQuery()方法可以將查詢到的結果存入Cursor對象中。然后,我們可以使用Cursor對象的getXXX()方法將查詢結果從Cursor對象中取出來。
當然了,我們還可以根據實際的需要,去實現更多的接口方法,比如,刪除數據、獲取數據列表、獲取數據個數等等。
封裝好了以上的這些接口方法,便可以很方便的在程序中直接調用這些方法,不必再去關心底層數據庫的調用,而將精力放在UI界面的設計實現上。