Android中SQLite數據操作
1. 概述
SQLite 為嵌入式系統上的一個開源數據庫管理系統,它支持標準的關系型數據庫查詢語句SQL 語法,支持事務(Transaction) 操作 。而且SQLite 數據庫在Andrioid平臺上大約只需要250K的內存空間。在Android平臺上無需任何數據庫設置和管理,你只需使用SQL語句來訪問 Database,SQL自動為你管理數據庫。在Android平臺上使用數據庫可能比較慢,這是因為訪問數據庫涉及的大量的讀寫操作(一般在SD卡)。 因此從性能上考慮,建議不在UI線程進行訪問數據庫的操作,可以使用AsyncTask來進行數據庫的讀寫。SQLite 支持的數據類型有 TEXT (類似Java中的String 類型),INTEGER(類似Java中的long 類型)以及REAL (類似Java 中的double類型),所有其它數據類型最終都必須轉化成這三種類型之一才能存放到數據庫中。要注意的是SQLite 本身不校驗字段的數據類型,也就是說你可以將整數寫到字符串字段中。
如果你的應用創建一個SQLite數據庫,它的缺省路徑為”DATA/data/APP_NAME/databases/FILENAME”.
說明:
? DATA 為使用Environment.getDataDirectory()返回的路徑,一般為你的SD卡的路徑。? APP_Name為你的應用的名稱
? FILENAME為你的數據庫的文件名。
一般來說,應用創建的數據庫只能有創建它的應用訪問,如果你想共享你的數據,你可以使用Content provider 來實現。
2. android.database.sqlite 類定義
Sqlite 是以庫函數的形式提供的,而不是以單獨的進程來提供數據庫服務(如Desktop平臺上SQL Server,這樣做的效果是,由應用程序創建的SQLite數據庫成為應用的一個部分,從而降低了外部依賴,減小數據訪問的延遲,簡化了數據的事務處理 時的同步和鎖定操作。在Android平臺上SQLite 支持定義在android.database.sqlite (其實是Android系統中SQLite C函數的Java接口),其主要的類和接口的類關系圖說明如下:
? 其中ContentValues 定義在包android.content 包中,可以用來做數據庫表插入一行,每個ContentValues對象可以表示數據表中的一行,為一組列名和列值的集合。
? 所有SQL 查詢的返回結果為一個Cursor對象,使用Cursor(游標)對象可以訪問返回的數據集中每行,前進或后退 如moveToFirst, moveToNext, getCount ,getColumnName 等。
? SQLiteOpenHelper 為一抽象類,它簡化了數據庫的創建,打開,升級操作。
? SQLiteDatabase 代表一個數據庫對象,提供來inert, update ,delete 以及execSQL 等操作來查詢,讀寫數據庫。
? SQLiteQuery 代表一個查詢,一般不能直接使用,而是由SQLiteCursor使用。
? SQLiteStatement 代表一個預編譯的數據庫查詢(類似stored proc).
? SQLiteQueryBuild 為一輔助類,用于幫助創建SQL查詢。
注:本教程不涉及SQL 語句本身的用法,假定你以有基本的數據庫SQL知識,教程側重如何在Android平臺上使用SQLite數據庫。
3. 創建數據庫
我們將使用一個TodoList 為例介紹SQLite 的基本用法,在設計SQLite數據庫表時有幾點建議:
? 對于一些文件資源(如圖像或是聲音等)不建議直接存放在數據庫表中,而今存儲這些資源對應的文件名或是對于的content provider 使用的URL 全名。
? 盡管不是強制的,在設計數據庫的關鍵字時,所有表都包含一個_id 域做為表的關鍵字,它是一個自動增長的整數域。 如果使用SQLite 作為一個Content Provider,此時唯一的_id域就是必須的。
設計Todolist的數據庫結構如下:
前面說過Android SDK中提供了一個SQLiteOpenHelper來幫助創建,打開,和管理數據庫,一個比較常見的設計模式是創建一個數據庫的Adpater,為應用程序訪問數據庫提供一個抽象層,該層可以封裝與數據庫之間的直接交互:
按照上面的原則,我們為TodoList 數據庫創建兩個類:ToDoDBAdapter和ToDoDBOpenHelper ,其中ToDoDBOpenHelper作為ToDoDBAdapter 內部類定義(也可以作為單獨類)。構造創建數據庫的SQL 語句,初始的類定義如下:
public class ToDoDBAdapter {private static final String DATABASE_NAME = "todoList.db"; private static final String DATABASE_TABLE = "todoItems"; private static final int DATABASE_VERSION = 1;
private SQLiteDatabase mDb; private final Context mContext;
public static final String KEY_ID = "_id"; public static final String KEY_TASK = "task"; public static final int TASK_COLUMN = 1;
public static final String KEY_CREATION_DATE = "creation_date"; public static final int CREATION_DATE_COLUMN = 2;
private ToDoDBOpenHelper dbOpenHelper;
public ToDoDBAdapter(Context context) { mContext = context; dbOpenHelper = new ToDoDBOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION); }
public void close() { mDb.close(); }
public void open() throws SQLiteException { try { mDb = dbOpenHelper.getWritableDatabase(); } catch (SQLiteException ex) { mDb = dbOpenHelper.getReadableDatabase(); } }
private static class ToDoDBOpenHelper extends SQLiteOpenHelper {
public ToDoDBOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); }
// SQL statement to create a new database private static final String DATABASE_CREATE = "create table "
- DATABASE_TABLE + " (" + KEY_ID
- " integer primary key autoincrement , " + KEY_TASK
- " text not null, " + KEY_CREATION_DATE + " long);";
@Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE);
}
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w("TaskDBAdapter", "Upgrading from version " + oldVersion
- " to " + newVersion);
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE); onCreate(db);
}
}
}</pre>
ToDoDBOpenHelper 為 SQLiteOpenHelper 的子類,一般需要重載onCreate(SQLiteDatabase) 和 onUpgrade(SQLiteDabase, int ,int) 如有需要也可以重載onOpen (SQLiteDatabase), 這個類可以在數據庫未創建時自動創建數據庫,如果已有數據庫則可以打開。
它提供了兩個方法getReadableDatabase() 和getWriteableDatabase() 來取得SQLiteDatabase數據庫對象,此時 SQLiteOpenHelper 會根據需要創建onCreate 或升級onUpdate 數據庫,如果數據庫已存在,則打開對應的數據庫。 盡管是兩個方法,通常兩個方法返回時是同一個數據庫對象,除非在出現問題時(如磁盤空間滿)此時getReadableDatabase() 可能返回只讀數據庫對象。
ToDoDBAdapter 的open 方法首先是試圖獲取一個可讀寫的數據庫對象,如果不成功,則再試圖取得一個只讀類型數據庫。
4. 讀寫數據庫操作
有了數據庫對象之后,可以使用execSQL 提供SQL語言來添加,刪除,修改或查詢數據庫。除了通用的execSQL 之外,SQLiteDatabase 提供了 insert, update, delete ,query 方法來簡化數據庫的添加,刪除,修改或查詢操作。
此外SQLite不強制檢測數據庫的數據類型,通過DBAdapter 可以使用強數據類型來修改,刪除數據等,這也是使用DBAdapter的一個好處:
這里我們先定義了一個TodoItem 類,表示一個Todo 項:
public class TodoItem {private String mTask;
private Date mCreated;
public String getTask(){ return mTask; }
public Date getCreated(){ return mCreated; }
public TodoItem(String task){ this(task,new Date(System.currentTimeMillis())); }
public TodoItem(String task,Date created){ mTask=task; mCreated=created; }
@Override public String toString(){ SimpleDateFormat sdf=new SimpleDateFormat("dd/mm/yy"); String dateString= sdf.format(mCreated); return "("+ dateString+ ")" + mTask; }
}</pre>
下面的方法提供使用TodoItem類型做參數使用添加,刪除和修改TodoItem//insert a new task public long insertTask(TodoItem task){ ContentValues newTaskValues=new ContentValues(); //assign values for each row newTaskValues.put(KEY_TASK, task.getTask()); newTaskValues.put(KEY_CREATION_DATE, task.getCreated().getTime());//insert row return mDb.insert(DATABASE_TABLE,null,newTaskValues); }
public boolean removeTask(long rowIndex){ return mDb.delete(DATABASE_TABLE,KEY_ID+"="+rowIndex, null)>0; }
public boolean updateTask(long rowIndex,String task){ ContentValues newValue=new ContentValues(); newValue.put(KEY_TASK, task); return mDb.update(DATABASE_TABLE, newValue, KEY_ID+"="+rowIndex, null)>0; }</pre>
其中ContentValues 定義了列名到列值的映射,類似于Hashtable。
SQLiteDatabase 的Query方法的一個定義如下:
public Cursorquery(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
? table : 數據庫名稱
? columns: 需要返回的列名稱,
? selection: 查詢條件,為WHERE語句(不含WHERE)。
? selectionArgs: 如果selection 中帶有? ,這里可以給出? 的替代值。
? groupBy: Group 語句除去GROUP BY。
? having: Having語句除去Having
? OrderBy: Order by 語句。
下面代碼給出查詢某個todoItem 項:public Cursor getAllToDoItemsCursor(){ return mDb.query(DATABASE_TABLE, new String[]{KEY_ID,KEY_TASK,KEY_CREATION_DATE}, null, null, null, null, null); }public Cursor setCursorToToDoItem(long rowIndex) throws SQLException { Cursor result=mDb.query(DATABASE_TABLE, new String[]{KEY_ID,KEY_TASK}, KEY_ID+"="+rowIndex, null, null, null, null); if(result.getCount()==0 || !result.moveToFirst()){ throw new SQLException ("No to do item found for row:"
- rowIndex); } return result; }
public TodoItem getToDoItem(long rowIndex) throws SQLException { Cursor cursor=mDb.query(DATABASE_TABLE, new String[]{KEY_ID,KEY_TASK,KEY_CREATION_DATE}, KEY_ID+"="+rowIndex, null, null, null, null); if(cursor.getCount()==0 || !cursor.moveToFirst()){ throw new SQLException ("No to do item found for row:"
- rowIndex); } String task=cursor.getString(TASK_COLUMN); long created=cursor.getLong(CREATION_DATE_COLUMN); TodoItem result=new TodoItem(task,new Date(created)); return result;
}</pre>
Query方法還有幾個重載的方法,具體可以參見Android文檔
5. 導出數據庫到XML 文件
使用SQLiteOpenHelper 創建的數據庫為應用程序私有,其路徑一般為DATA/data/APP_NAME/databases/FILENAME
? DATA 為使用Environment.getDataDirectory()返回的路徑,一般為你的SD卡的路徑。
? APP_Name為你的應用的名稱
? FILENAME為你的數據庫的文件名
其它程序一般無法訪問這個文件,因此也給調試帶來了不便,當然你可以使用Android SDK 的sqlite3 工具來直接訪問這個數據,但個人還是覺的sqlite 使用起來不是十分方便。
你也可以將數據庫創建者SD卡上面,此時可以使用SQLiteDatabase 的openOrCreateDatabase 指定要創建的數據庫的文件名(指定SD卡上的某個文件名)。
也可以將數據庫使用代碼復制到SD卡上。 此時可以使用一些桌面系統上的SQLite管理工具,比如Firefox 的SQL Lite manager 插件來訪問這個數據庫。
一種簡潔的方法是將數據庫導出到XML文件,下面類DatabaseDump的實現,可以將如何一個數據庫所有表和表的內容導出到XML文件中。
import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase;class DatabaseDump {
public DatabaseDump(SQLiteDatabase db,String destXml) { mDb = db; mDestXmlFilename=destXml;
try { // create a file on the sdcard to export the // database contents to File myFile = new File(mDestXmlFilename); myFile.createNewFile();
FileOutputStream fOut = new FileOutputStream(myFile); BufferedOutputStream bos = new BufferedOutputStream(fOut);
mExporter = new Exporter(bos); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
public void exportData() {
try { mExporter.startDbExport(mDb.getPath());
// get the tables out of the given sqlite database String sql = "SELECT * FROM sqlite_master";
Cursor cur = mDb.rawQuery(sql, new String[0]); cur.moveToFirst();
String tableName; while (cur.getPosition() < cur.getCount()) { tableName = cur.getString(cur.getColumnIndex("name"));
// don't process these two tables since they are used // for metadata if (!tableName.equals("android_metadata") && !tableName.equals("sqlite_sequence")) { exportTable(tableName); }
cur.moveToNext(); } mExporter.endDbExport(); mExporter.close(); } catch (IOException e) { e.printStackTrace(); } }
private void exportTable(String tableName) throws IOException { mExporter.startTable(tableName);
// get everything from the table String sql = "select * from " + tableName; Cursor cur = mDb.rawQuery(sql, new String[0]); int numcols = cur.getColumnCount();
cur.moveToFirst();
// move through the table, creating rows // and adding each column with name and value // to the row while (cur.getPosition() < cur.getCount()) { mExporter.startRow(); String name; String val; for (int idx = 0; idx < numcols; idx++) { name = cur.getColumnName(idx); val = cur.getString(idx); mExporter.addColumn(name, val); }
mExporter.endRow(); cur.moveToNext(); }
cur.close();
mExporter.endTable(); }
private String mDestXmlFilename = "/sdcard/export.xml";
private SQLiteDatabase mDb; private Exporter mExporter;
class Exporter { private static final String CLOSING_WITH_TICK = "'>"; private static final String START_DB = "<export-database name='"; private static final String END_DB = "</export-database>"; private static final String START_TABLE = "<table name='"; private static final String END_TABLE = "</table>"; private static final String START_ROW = "<row>"; private static final String END_ROW = "</row>"; private static final String START_COL = "<col name='"; private static final String END_COL = "</col>";
private BufferedOutputStream mbufferos;
public Exporter() throws FileNotFoundException { this(new BufferedOutputStream(new FileOutputStream(mDestXmlFilename)));
}
public Exporter(BufferedOutputStream bos) { mbufferos = bos; }
public void close() throws IOException { if (mbufferos != null) { mbufferos.close(); } }
public void startDbExport(String dbName) throws IOException { String stg = START_DB + dbName + CLOSING_WITH_TICK; mbufferos.write(stg.getBytes()); }
public void endDbExport() throws IOException { mbufferos.write(END_DB.getBytes()); }
public void startTable(String tableName) throws IOException { String stg = START_TABLE + tableName + CLOSING_WITH_TICK; mbufferos.write(stg.getBytes()); }
public void endTable() throws IOException { mbufferos.write(END_TABLE.getBytes()); }
public void startRow() throws IOException { mbufferos.write(START_ROW.getBytes()); }
public void endRow() throws IOException { mbufferos.write(END_ROW.getBytes()); }
public void addColumn(String name, String val) throws IOException { String stg = START_COL + name + CLOSING_WITH_TICK + val + END_COL; mbufferos.write(stg.getBytes()); } }
}</pre>