Android SQLite 數據庫存儲詳解
SQLite 是一款輕量級的關係型數據庫
Android爲了讓我們能夠更加方便地管理數據庫,專門提供了一個SQLiteOpenHelper幫助類,藉助這個類就可以非常簡單地對數據庫進行創建和升級。
SQLiteOpenHelper是一個抽象類。
SQLiteOpenHelper中有兩個抽象方法,分別是onCreate()和onUpgrade()。
SQLiteOpenHelper中還有兩個非常重要的實例方法,getReadableDatabase()和getWritableDatabase()。兩者的區別參考
Android SQLiteOpenHelper 實例方法getReadableDatabase()和getWritableDatabase() 區別
數據庫文件存放在 : /data/data/<package name>/databases/ 目錄下
SQLite不像其他的數據庫擁有衆多繁雜的數據類型,它的數據類型很簡單,integer表示整型,real表示浮點型,text表示文本類型,blob表示二進制類型。
SQLiteOpenHelper中有兩個構造方法可供重寫。
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
第三個參數允許我們在查詢數據的時候返回一個自定義的Cursor,一般都是傳入null。
創建數據庫
舉個栗子:
創建一個BookStore.db的數據庫。然後在這個數據庫中新建一張Book表,表中有id(主鍵)、作者、價格、頁數和書名等列。
建表語句
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
建表語句中我們還使用了primary key將id列設爲主鍵,並用autoincrement關鍵字表示id列是自增長的。
升級數據庫
再添加一張Category表用於記錄書籍的分類。
Category表中有id(主鍵)、分類名和分類代碼
create table Category (
id integer primary key autoincrement,
category_name text,
category_code integer)
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT). show();
}
點擊Create Database 按鈕,查看數據後發現,Category表並沒有創建。
因爲此時BookStore.db數據庫已經存在了,之後不管我們怎樣點擊Create database按鈕,MyDatabaseHelper中的onCreate()方法都不會再次執行,因此新添加的表也就無法得到創建了。
需要更改update方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
在onUpgrade()方法中執行了兩條DROP語句,如果發現數據庫中已經存在Book表或Category表了,就將這兩張表刪除掉,然後再調用onCreate()方法去重新創建。
更改數據庫版本號後,運行項目。即可更新數據庫。創建了Category表。
但是這種方法是相當粗暴。直接刪表。在正常的產品項目中是不可能這麼幹的。
正確的更新數據庫方法是:
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
在onCreate()方法裏我們新增了一條建表語句,然後又在onUpgrade()方法中添加了一個switch判斷,如果用戶當前數據庫的版本號是1,就只會創建一張Category表。這樣當用戶是直接安裝的第二版的程序時,就會將兩張表一起創建。而當用戶是使用第二版的程序覆蓋安裝第一版的程序時,就會進入到升級數據庫的操作中,此時由於Book表已經存在了,因此只需要創建一張Category表即可。
ADB 查詢數據庫
如果使用FileExplorer ,最多隻能看到databases目錄下有一個BookStore.db 文件。
要查詢數據庫數據,需要 使用 adb shell 。
adb.exe 存放在sdk的platform-tools目錄下,如果想要在命令行中使用這個工具,就需要先把它的路徑配置到環境變量裏。
如果你使用的是Windows系統,可以右擊我的電腦→屬性→高級→環境變量,然後在系統變量裏找到Path並點擊編輯,將platform-tools目錄配置進去。如果要使用真機調試程序,真機需要root權限。
配置好環境變量後,輸入 adb shell 。如果出現如下格式。說明adb 指令權限不夠。再輸入 su 指令即可。
C:\Users\Tan>adb shell
shell@cancro:/ $
輸入su 後,顯示:
C:\Users\Tan>adb shell
shell@cancro:/ $ su
root@cancro:/ #
進入數據後,用sqlite3 指令增刪改查數據時,會提示sqlite3 不是內部指令。這是因爲手機中沒有sqlite3 文件。
解決辦法:參考如何設置Android手機的sqlite3命令環境
如果條件允許可以使用模擬器。模擬器是支持sqlite3指令集的。
也可以使用【RootExplorerRE文件管理器.apk】 ,安裝到手機上直接查看手機data/data/目錄下的文件。查看數據庫。但是需要手機root權限。
下載地址:下載鏈接
也可以將 數據庫文件導出,然後利用第三方插件查看。參考:Android開發工具---SQLiteManager插件
輸入 adb shell
然後使用cd命令進行到 /data/data/com.example.databasetest/databases/目錄下 (注意cd 後面有空格)
使用ls命令查看到該目錄裏的文件
接下來我們就要藉助sqlite命令來打開數據庫了,只需要鍵入sqlite3,後面加上數據庫名即可。 sqlite3 BookStore.db
進入數據庫後,
輸入.table 查看數據庫中的表
輸入.schema 查看建表語句
鍵入.exit或.quit命令可以退出數據庫的編輯,再鍵入exit命令就可以退出設備控制檯了。
添加數據
我們可以對數據進行的操作也就無非四種,即CRUD。其中C代表添加(Create),R代表查詢(Retrieve),U代表更新(Update),D代表刪除(Delete)。每一種操作又各自對應了一種SQL命令,添加數據時使用insert,查詢數據時使用select,更新數據時使用update,刪除數據時使用delete。
方法一:
insert()方法
接收三個參數,第一個參數是表名,我們希望向哪張表裏添加數據,這裏就傳入該表的名字。
第二個參數用於在未指定添加數據的情況下給某些可爲空的列自動賦值NULL,一般我們用不到這個功能,直接傳入null即可。
第三個參數是一個ContentValues對象,它提供了一系列的put()方法重載,用於向ContentValues中添加數據,只需要將表中的每個列名以及相應的待添加數據傳入即可。
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 開始組裝第一條數據
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一條數據
values.clear();
// 開始組裝第二條數據
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); // 插入第二條數據
}
});
方法二、如果擅長SQL語句也可用如下方法
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
db.execSQL 方法:
用adb 進入BookStores.db 數據庫。輸入查詢語句 select * from Book ,即可查看錶中的數據。
方法三:在adb 指令中,用sql 語句增加數據。
adb shell
cd /data/data/com.example.databasetest/databases/ //進入數據庫目錄
ls //查看目錄下文件
sqlite3 BookStore.db // 利用sqlite3 指令進入數據庫
.table //查看數據庫中的表
select * from Book; //查詢Book表中的數據
insert into Book ( name , author , pages , price) values ('diyihangdaima' , 'guolin', '550' ,'70.5');
//向Book表中插入一條數據
// 注意 ,用 insert into 指令 ,key 值和values 值 不能用 new String[] {"diyihsangdaima "……} 這種格式。並且,values中的值用單引號。
select * from Book; //查詢Book表中的數據
更新數據
方法一:
update() ; 方法
方法接收四個參數,
第一個參數和insert()方法一樣,也是表名,在這裏指定去更新哪張表裏的數據。
第二個參數是ContentValues對象,要把更新數據在這裏組裝進去。
第三、第四個參數用於去約束更新某一行或某幾行中的數據,不指定的話默認就是更新所有行。
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });
}
});
這裏使用了第三、第四個參數來指定具體更新哪幾行。第三個參數對應的是SQL語句的where部分,表示去更新所有name等於?的行,而?是一個佔位符,可以通過第四個參數提供的一個字符串數組爲第三個參數中的每個佔位符指定相應的內容。
方法二:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
在cmd中用sqlite3 指令更新數據;
sqlite> update Book set price = '9.99' where id = 1; //更新ID=1 的數據
sqlite> update Book set price = '50' where author = 'guolin'; // 更新author 爲guolin的數據
刪除數據
方法一:
delete()方法
方法接收三個參數,
第一個參數仍然是表名
第二、第三個參數又是用於去約束刪除某一行或某幾行的數據,不指定的話默認就是刪除所有行
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
}
});
方法二:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
sqlite> delete from Book where id = 1;
sqlite> delete from Book where author = 'guolin';
查詢數據
SQL的全稱是Structured Query Language,翻譯成中文就是結構化查詢語言
方法一:
query();
query()方法用於對數據進行查詢。這個方法的參數非常複雜,最短的一個方法重載也需要傳入七個參數。那我們就先來看一下這七個參數各自的含義吧,
第一個參數不用說,當然還是表名,表示我們希望從哪張表中查詢數據。
第二個參數用於指定去查詢哪幾列,如果不指定則默認查詢所有列。
第三、第四個參數用於去約束查詢某一行或某幾行的數據,不指定則默認是查詢所有行的數據。
第五個參數用於指定需要去group by的列,不指定則表示不對查詢結果進行group by操作。
第六個參數用於對group by之後的數據進行進一步的過濾,不指定則表示不進行過濾。
第七個參數用於指定查詢結果的排序方式,不指定則表示使用默認的排序方式。
query()方法參數 |
對應SQL部分 |
描述 |
table |
from table_name |
指定查詢的表名 |
columns |
select column1, column2 |
指定查詢的列名 |
selection |
where column = value |
指定where的約束條件 |
selectionArgs |
- |
爲where中的佔位符提供具體的值 |
groupBy |
group by column |
指定需要group by的列 |
having |
having column = value |
對group by後的結果進一步約束 |
orderBy |
order by column1, column2 |
指定查詢結果的排序方式 |
Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查詢Book表中所有的數據
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍歷Cursor對象,取出數據並打印
String name = cursor.getString(cursor. getColumnIndex("name"));
String author = cursor.getString(cursor. getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex ("pages"));
double price = cursor.getDouble(cursor. getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close(); // 不關閉會導致內存泄漏
}
});
查詢完之後就得到了一個Cursor對象,接着我們調用它的moveToFirst()方法將數據的指針移動到第一行的位置,然後進入了一個循環當中,去遍歷查詢到的每一行數據。在這個循環中可以通過Cursor的getColumnIndex()方法獲取到某一列在表中對應的位置索引,然後將這個索引傳入到相應的取值方法中,就可以得到從數據庫中讀取到的數據了。接着我們使用Log的方式將取出的數據打印出來,藉此來檢查一下讀取工作有沒有成功完成。最後別忘了調用close()方法來關閉Cursor。
方法二:
db.rawQuery("select * from Book", null);
方法三:
sqlite
select * from Book;
sqlite> select price from Book;
19.95
16.96
19.95
使用事務
Button replaceData = (Button) findViewById(R.id.replace_data);
replaceData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 開啓事務
try {
db.delete("Book", null, null);
if (true) {
// 在這裏手動拋出一個異常,讓事務失敗
throw new NullPointerException();
}
ContentValues values = new ContentValues();
values.put("name", "Game of Thrones");
values.put("author", "George Martin");
values.put("pages", 720);
values.put("price", 20.85);
db.insert("Book", null, values);
db.setTransactionSuccessful(); // 事務已經執行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction(); // 結束事務
}
}
});
首先調用SQLiteDatabase的beginTransaction()方法來開啓一個事務,然後在一個異常捕獲的代碼塊中去執行具體的數據庫操作,當所有的操作都完成之後,調用setTransactionSuccessful()表示事務已經執行成功了,最後在finally代碼塊中調用endTransaction()來結束事務。注意觀察,我們在刪除舊數據的操作完成後手動拋出了一個NullPointerException,這樣添加新數據的代碼就執行不到了。不過由於事務的存在,中途出現異常會導致事務的失敗,此時舊數據應該是刪除不掉的。
數據庫升級最佳寫法
給Book表和Category表之間建立關聯,需要在Book表中添加一個category_id的字段。
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "category_id integer)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
}
在Book表的建表語句中添加了一個category_id列。
在onUpgrade()方法裏,我們添加了一個新的case,如果當前數據庫的版本號是2,就會執行alter命令來爲Book表新增一個category_id列。
switch中每一個case的最後都是沒有使用break的!