一、前期基礎(chǔ)知識(shí)儲(chǔ)備
Android本地化存儲(chǔ)三種方式:
①文件存儲(chǔ)吗讶,存儲(chǔ)簡單二進(jìn)制數(shù)據(jù)和文本數(shù)據(jù)袖裕;
②SharedPreference存儲(chǔ)曹抬,鍵值對(duì)存儲(chǔ),存儲(chǔ)數(shù)據(jù)類型多樣急鳄,存儲(chǔ)方式單一谤民;
③SQLite數(shù)據(jù)庫存儲(chǔ),Android內(nèi)置數(shù)據(jù)庫疾宏,存儲(chǔ)復(fù)雜關(guān)系型數(shù)據(jù)张足,質(zhì)的飛躍。
SQLite正式發(fā)布于2000年灾锯,現(xiàn)在Android內(nèi)置的是SQLite3.0兢榨。占用幾百KB資源,但運(yùn)算速度飛快顺饮,非常適合在移動(dòng)設(shè)備上使用。SQLite數(shù)據(jù)庫使用時(shí)會(huì)涉及到三個(gè)類:
①SQLiteOpenHelper:Android系統(tǒng)提供的操作SQLite數(shù)據(jù)庫的方式凌那。工具類 抽象類兼雄,我們通過繼承該類,然后重寫數(shù)據(jù)庫創(chuàng)建以及更新的方法帽蝶,我們還可以通過該類的對(duì)象獲得數(shù)據(jù)庫實(shí)例或者關(guān)閉數(shù)據(jù)庫赦肋。
②SQliteDatabase:平常數(shù)據(jù)庫操作方式使用的類,數(shù)據(jù)庫訪問類——我們可以通過該類的對(duì)象來直接使用操作數(shù)據(jù)庫的方式對(duì)數(shù)據(jù)庫做增刪改查的操作。(見用于佃乘,熟悉數(shù)據(jù)庫語法的大牛)
③Cursot:游標(biāo)囱井,有點(diǎn)類似于JDBC里的resultset,結(jié)果集趣避!可以簡單理解為指向數(shù)據(jù)庫中某一記錄的指針庞呕。使用可以同平常數(shù)據(jù)庫一樣,也可以使用Android系統(tǒng)提供的操作方式程帕。
本篇博客住练,主要分析Android系統(tǒng)內(nèi)置的操作數(shù)據(jù)庫的方式,所以打交道的類只有兩個(gè):SQLiteOpenHelper和Cursot愁拭。
二讲逛、上代碼,具體分析
1)使用SQLiteOpenHelper創(chuàng)建數(shù)據(jù)庫
①SQLiteOpenHelper 是一個(gè)抽象類岭埠,如果需要使用它的話盏混,就需要?jiǎng)?chuàng)建一個(gè)幫助類去繼承它;
②抽象父類SQLiteOpenHelper 中有兩個(gè)抽象方法:onCreate() 和 onUpdate()惜论,需要在子類里重寫這兩個(gè)方法括饶,然后分別在這兩個(gè)方法中去實(shí)現(xiàn)創(chuàng)建和升級(jí)數(shù)據(jù)庫的邏輯;
新建 MyDatabaseHelper 類繼承自 SQLiteOpenHelper来涨,代碼如下:
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 )";
/**
* integer:整形
* real:浮點(diǎn)型
* text:文本類型
* blob:二進(jìn)制類型
* PRIMARY KEY將id列設(shè)置為主鍵
* AutoIncrement關(guān)鍵字表示id列是自動(dòng)增長的
*/
private Context myContent;
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
myContent = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
//創(chuàng)建數(shù)據(jù)庫的同時(shí)創(chuàng)建Book表
db.execSQL(CREATE_BOOK);
//提示數(shù)據(jù)庫創(chuàng)建成功
Toast.makeText(myContent, "數(shù)據(jù)庫創(chuàng)建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
③SQLiteOpenHelper 中另外兩個(gè)非常重要的實(shí)例方法图焰,getReadableDatabase() (查)和 getWritableDatabase()(增刪改)。這兩種方法都可以創(chuàng)建或打開一個(gè)現(xiàn)有的數(shù)據(jù)庫(如果數(shù)據(jù)庫已存在則直接打開蹦掐,否則創(chuàng)建一個(gè)新的數(shù)據(jù)庫)技羔,并返回一個(gè)可對(duì)數(shù)據(jù)庫進(jìn)行增刪改查操作的對(duì)象;
④子類需要重寫父類SQLiteOpenHelper 的兩個(gè)構(gòu)造方法卧抗,第二個(gè)構(gòu)造方法接收四個(gè)參數(shù)藤滥,第一個(gè)參數(shù)是 Context,必須要有Context對(duì)象才能對(duì)數(shù)據(jù)庫進(jìn)行操作社裆。第二個(gè)參數(shù)是數(shù)據(jù)庫名拙绊,創(chuàng)建數(shù)據(jù)庫時(shí)使用的就是這里指定的名稱。第三個(gè)參數(shù)允許在查詢數(shù)據(jù)庫的時(shí)候返回一個(gè)自定義的 Cursor泳秀,一般傳入null标沪。第四個(gè)參數(shù)表示當(dāng)前數(shù)據(jù)庫的版本號(hào),可用于對(duì)數(shù)據(jù)庫進(jìn)行升級(jí)操作嗜傅。
構(gòu)建出 SQLiteOpenHelper 的實(shí)例之后金句,再調(diào)用它的 getReadableDatabase() 或 getWritableDatabase() 方法就能夠創(chuàng)建數(shù)據(jù)庫了,數(shù)據(jù)庫文件會(huì)存放在 /data/data/<包名>/database/ 目錄下吕嘀。利用Android Studio右下角工具可直接查看违寞。
在 MainActivity 中進(jìn)行測(cè)試贞瞒,代碼如下:
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//構(gòu)建一個(gè) MyDatabaseHelper 對(duì)象,通過構(gòu)造函數(shù)將數(shù)據(jù)庫名指定為 BookStore.db
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
Button createDatabase = (Button)findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
*調(diào)用getWritableDatabase() 方法
* 自動(dòng)檢測(cè)當(dāng)前程序中 BookStore.db 這個(gè)數(shù)據(jù)庫
* 如果不存在則創(chuàng)建該數(shù)據(jù)庫并調(diào)用 onCreate() 方法
* 同時(shí)Book表也會(huì)被創(chuàng)建
*/
dbHelper.getWritableDatabase();
}
});
}
}
點(diǎn)擊按鈕 BookStore.db 數(shù)據(jù)庫和 Book 表就已經(jīng)創(chuàng)建成功了趁曼,再次點(diǎn)擊不會(huì)再有Toast彈出军浆。
2)使用SQLiteOpenHelper升級(jí)數(shù)據(jù)庫
onUpdate() 方法是用于對(duì)數(shù)據(jù)庫進(jìn)行升級(jí)的,它在整個(gè)數(shù)據(jù)庫的管理工作中擔(dān)當(dāng)著非常重要的作用挡闰。
目前 BookStore.db 中已經(jīng)有一張 Book 表用于存放書的各種詳細(xì)數(shù)據(jù)乒融,接下來再添加一張 Category 表用于記錄書籍的分類。將建表語句添加到 MyDatabaseHelper 中尿这,代碼如下:
public static final String CREATE_CATEGORY = "CREATE TABLE category ("
+ "id integer PRIMARY KEY Autoincrement , "
+ "category_name text , "
+ "category_code integer )";
并在 onCreate()方法中添加:db.execSQL( CREATE_CATEGORY); 這條語句簇抵,運(yùn)行程序,并不會(huì)彈出創(chuàng)建成功的提示射众。因?yàn)榇藭r(shí) BookStore.db 數(shù)據(jù)庫已經(jīng)存在了碟摆,之后不論怎樣點(diǎn)擊創(chuàng)建按鈕,MyDatabaseHelper 中的 onCreate() 方法都不會(huì)再次執(zhí)行叨橱,因此新添加的表也就無法得到創(chuàng)建了典蜕。
只需要巧妙的運(yùn)用 SQLiteOpenHelper 的升級(jí)功能就可以很輕松的解決這個(gè)問題。
修改 MyDatabaseHelper 中的代碼罗洗,如下所示:
@Override
public void onCreate(SQLiteDatabase db) {
//創(chuàng)建Book表和Category表
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
//提示數(shù)據(jù)庫創(chuàng)建成功
Toast.makeText(myContent, "數(shù)據(jù)庫創(chuàng)建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/**
* 如果發(fā)現(xiàn)數(shù)據(jù)庫中已經(jīng)存在 Book 表或 Category 表
* 就將這兩張表刪除掉愉舔,然后調(diào)用 onCreate() 方法重新創(chuàng)建
* 如果在創(chuàng)建表時(shí)發(fā)現(xiàn)表已經(jīng)存在,就會(huì)直接報(bào)錯(cuò)
*/
db.execSQL("DROP TABLE IF EXISTS Book");
db.execSQL("DROP TABLE IF EXISTS Category");
onCreate(db);
}
接下來的問題就是如何讓 onUpgrade() 方法能夠得到執(zhí)行了伙菜,還記得 SQLiteOpenHelper 的構(gòu)造方法里接受的第四個(gè)參數(shù)嗎轩缤?它表示當(dāng)前數(shù)據(jù)庫的版本號(hào),之前我們傳入的是1贩绕,現(xiàn)在只要傳入一個(gè)比1大的數(shù)火的,就可以讓 onUpgrade() 方法得到執(zhí)行了,修改MainActivity 中的代碼:
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
這里將數(shù)據(jù)庫版本號(hào)指定為2淑倾,表示我們對(duì)數(shù)據(jù)庫進(jìn)行升級(jí)了馏鹤。重新運(yùn)行程序,并點(diǎn)擊創(chuàng)建數(shù)據(jù)庫按鈕娇哆,這時(shí)就會(huì)再次彈出創(chuàng)建成功的提示湃累。
升級(jí)數(shù)據(jù)庫的最佳方式:
粗暴的刪除當(dāng)前所有的表來達(dá)到更新的效果,對(duì)于用戶來說是非常糟糕的碍讨,因?yàn)橐郧俺绦蛑写鎯?chǔ)的本地?cái)?shù)據(jù)全部丟失了治力。其實(shí)只需進(jìn)行一些合理的控制,就可以保證升級(jí)數(shù)據(jù)庫的時(shí)候數(shù)據(jù)并不會(huì)丟失了垄开。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
每一個(gè)數(shù)據(jù)庫版本都會(huì)對(duì)應(yīng)一個(gè)版本號(hào)琴许,當(dāng)指定的數(shù)據(jù)庫版本號(hào)大于當(dāng)前數(shù)據(jù)庫版本號(hào)的時(shí)候,就會(huì)進(jìn)入到 onUpgrade()方法中去執(zhí)行更新操作溉躲。這里需要為每一個(gè)版本號(hào)賦予它各自改變的內(nèi)容榜田,然后在 onUpgrade()方法中對(duì)當(dāng)前數(shù)據(jù)庫的版本進(jìn)行判斷,再執(zhí)行相應(yīng)的改變就可以了锻梳。
注意箭券,switch 中每一個(gè) case 的最后都是沒有使用 break 的,這是為了保證跨版本升級(jí)的時(shí)候疑枯,每一次的數(shù)據(jù)庫修改都能被全部執(zhí)行辩块。使用這種方式來維護(hù)數(shù)據(jù)庫的升級(jí),不管版本怎樣更新荆永,都可以保證數(shù)據(jù)庫的表結(jié)構(gòu)是最新的废亭,而且表中的數(shù)據(jù)也完全不會(huì)丟失。
3)使用SQLiteOpenHelper升級(jí)數(shù)據(jù)庫
注:這里使用的Android內(nèi)置的一系列輔助方法具钥,即使用SQLiteOpenHelper操作數(shù)據(jù)庫豆村,并不是使用SQliteDatabase直接操作數(shù)據(jù)表。
①添加數(shù)據(jù)-insert()
SQLiteDatabase 中提供了一個(gè) insert() 方法骂删,用于添加數(shù)據(jù)掌动。
insert() 方法接收三個(gè)參數(shù),第一個(gè)參數(shù)是表名宁玫,我們希望向哪張表里添加數(shù)據(jù)粗恢,這里就傳入該表的名字。第二個(gè)參數(shù)用于在未指定添加數(shù)據(jù)的情況下給某些可為空的列自動(dòng)賦值NULL欧瘪,一般用不到這個(gè)功能眷射,直接傳入 null 即可。第三個(gè)參數(shù)是一個(gè) ContentValues 對(duì)象佛掖,它提供了一系列的 put() 方法重載妖碉,用于向 ContentValues 中添加數(shù)據(jù),只需要將表中的每個(gè)列名及相對(duì)應(yīng)的待添加數(shù)據(jù)傳入即可苦囱。
MainActivity中代碼如下:
public void onClick(View v) {
//獲取 SQLiteDatabase 對(duì)象
SQLiteDatabase db = dbHelper.getWritableDatabase();
//使用ContentValues 對(duì)數(shù)據(jù)進(jìn)行組裝
ContentValues values = new ContentValues();
//開始組裝第一條數(shù)據(jù)
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
//插入第一條數(shù)據(jù)
db.insert("Book", null, values);
values.clear();
//開始組裝第二條數(shù)據(jù)
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
//插入第二條數(shù)據(jù)
db.insert("Book", null, values);
}
這里只對(duì)Book表里其中四列的數(shù)據(jù)進(jìn)行了組裝嗅绸,id并沒有賦值。因?yàn)閯?chuàng)建表的時(shí)候就將 id 列設(shè)置為自動(dòng)增長了撕彤,它的值會(huì)在入庫的時(shí)候自動(dòng)生成鱼鸠,不需要手動(dòng)賦值。
②更新數(shù)據(jù)-update()
SQLiteDatabase 中提供了一個(gè)非常好用的 update() 方法用于對(duì)數(shù)據(jù)進(jìn)行更新羹铅,這個(gè)方法接收四個(gè)參數(shù)蚀狰,第一個(gè)參數(shù)和 insert()方法一樣,也是表名职员,在這里指定去更新哪張表里的數(shù)據(jù)麻蹋。第二個(gè)參數(shù)是 ContentValues 對(duì)象,把要更新的數(shù)據(jù)在這里組裝進(jìn)去焊切。第三扮授、第四個(gè)參數(shù)用于去約束更新某一行的數(shù)據(jù)芳室,不指定的話默認(rèn)就是更新所有行。
MainActivity中代碼如下:
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
//?是一個(gè)占位符刹勃,通過字符串?dāng)?shù)組為每個(gè)占位符指定相應(yīng)的內(nèi)容
db.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"});
}
③刪除數(shù)據(jù)-delete()
SQLiteDatabase 中提供了一個(gè) delete()方法專門用于刪除數(shù)據(jù)堪侯,這個(gè)方法接收三個(gè)參數(shù),第一個(gè)參數(shù)仍然是表名荔仁,第二伍宦、第三個(gè)參數(shù)用于約束刪除某一行或某幾行的數(shù)據(jù),不指定的話默認(rèn)就是刪除所有行乏梁。
MainActivity中代碼如下:
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[]{"500"});
}
④查詢數(shù)據(jù)-query()次洼,會(huì)使用到第三個(gè)類Cursor游標(biāo)類
SQLiteDatabase 中提供了一個(gè) query() 方法用于對(duì)數(shù)據(jù)進(jìn)行查詢。這個(gè)方法的參數(shù)非常復(fù)雜遇骑,最短的一個(gè)方法重載也需要傳入七個(gè)參數(shù)卖毁。
query()方法參數(shù)及對(duì)應(yīng)SQL:
table:指定查詢的表名,對(duì)應(yīng) from table_name
columns:指定查詢的列名质蕉,對(duì)應(yīng) select column1,column2 ...
selection:指定 where 的約束條件势篡,where column = value
selectionArgs:為 where 中的占位符提供具體的值
groupBy:指定需要分組的列,group by column
having:對(duì)分組后的結(jié)果進(jìn)一步約束模暗,having column = value
orderBy:指定查詢結(jié)果的排序方式禁悠,order by column
雖然 query()方法的參數(shù)非常多,但是不必每條查詢語句都指定上所有的參數(shù)兑宇,多數(shù)情況下只需傳入少數(shù)幾個(gè)參數(shù)就可以完成查詢操作了碍侦。
調(diào)用 query()方法后會(huì)返回一個(gè) Cursor 對(duì)象,查詢到的所有數(shù)據(jù)都將從這個(gè)對(duì)象中取出隶糕。
MainActivity中代碼如下:
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//查詢Book表中的所有數(shù)據(jù)
Cursor cursor = db.query("Book", null, null, null, null, null, null, null);
//遍歷Curosr對(duì)象瓷产,取出數(shù)據(jù)并打印
while (cursor.moveToNext()) {
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("woider", "Book Name:" + name + " Author:"
+ author + " Pages:" + pages + " Price:" + price);
}
//關(guān)閉Cursor
cursor.close();
}
執(zhí)行 query()方法之后會(huì)得到一個(gè) Cursor對(duì)象,然后通過 Cursor 對(duì)象的 moveToNext()方法去遍歷查詢到的每一行數(shù)據(jù)枚驻。在這個(gè)循環(huán)中可以通過 Cursor 的 getColumnIndex() 方法獲取到某一列在表中對(duì)應(yīng)位置的索引濒旦,然后將這個(gè)索引傳入到相應(yīng)的取值方法中,就可以得到從數(shù)據(jù)庫中讀取到的數(shù)據(jù)了再登。
最后別忘了調(diào)用 close()方法來關(guān)閉 Cursor尔邓。(不關(guān)閉Cursor會(huì)造成內(nèi)存泄漏)
游標(biāo)Cursor的相關(guān)操作:
遍歷游標(biāo):
while(c.moveToNext()){
int id=c.getInt(c.getColumnName(列名ID));
String name=c.getString(c.getColumnIndex(字段名NAME));
}
常用方法:
(1)move(int offset);將指針上下移動(dòng)offset個(gè)記錄。若offset值為正數(shù)則向下移動(dòng)锉矢,若為負(fù)數(shù)則向上移動(dòng)梯嗽。
(2)boolean moveToFirst();指針移至結(jié)果集的第一個(gè)記錄。若移動(dòng)成功則返回true,否則返回false沽损。
(3)boolean moveToLast();指針移至結(jié)果集的最后一個(gè)記錄灯节。若移動(dòng)成功則返回true,否則返回false。
(4)boolean moveToNext();指針向后移動(dòng)一條記錄,若移動(dòng)成功則返回true,否則返回false炎疆。
(5)boolean moveToPrevious();指針向前移動(dòng)一條記錄卡骂,若移動(dòng)成功則返回true,否則返回false磷雇。
(6)isLast();若指針在Cursor結(jié)果集的最后一條記錄偿警,則返回true躏救,否則返回false唯笙。
(7)isAfterLast();若指針在Cursor結(jié)果集最后一條記錄之后,則返回true盒使,否則返回false崩掘。
(8)isFirst();若指針在Cursor結(jié)果集的第一條記錄,則返回true少办,否則返回false苞慢。
(9)isBeforeFirst();若指針在Cursor結(jié)果集的第一條記錄之前,則返回true英妓,否則返回false挽放。
(10)isClose();Cursot對(duì)象是否關(guān)閉,若關(guān)閉則返回true蔓纠,否則返回false辑畦。
(11)int getColumnIndex(String columnName);獲得指定列的索引值。
(12)String getString(int columnIndex);按列的索引獲取字符串類型的數(shù)據(jù)腿倚。
------------------------------------SQLiteOpenHelper分割線------------------------------------
4)SQLiteDatabase數(shù)據(jù)庫相關(guān)操作
上面的數(shù)據(jù)庫操作都是利用SQLiteOpenHelper進(jìn)行的纯出,有興趣的讀者也可嘗試下直接使用SQLite自帶的數(shù)據(jù)庫語言進(jìn)行數(shù)據(jù)庫的操作。
SQLiteDatabase數(shù)據(jù)庫類:提供了針對(duì)數(shù)據(jù)庫的增 刪 改 查的操作敷燎。
注:除了Cursor游標(biāo)對(duì)象暂筝、SQLiteDatabase數(shù)據(jù)庫對(duì)象也需要關(guān)閉。
SQLiteDatabase db=openOrCreateDatabase("students.db",MODE_PRIVATE,null);如果數(shù)據(jù)庫不存在則執(zhí)行創(chuàng)建操作硬贯,存在則執(zhí)行打開操作焕襟。
db.execSQL("create table if not exists student (_id integer primary key autoincrement,name varchar(20),age integer)");創(chuàng)建數(shù)據(jù)表,如果存在則不創(chuàng)建饭豹。
db.execSQL("insert into student(_id,name,age)values(null,?,?)",new Object[]{"張立冬",25});插入數(shù)據(jù)鸵赖;
db.execSQL("delete from student where name='張立冬'");刪除數(shù)據(jù);
db.execSQL("update student set name=?,age=?",new Object[]{"小明",18});修改數(shù)據(jù)墨状;
Cursor c=db.rawQuery("select * from student",null);查詢數(shù)據(jù)——返回游標(biāo)(結(jié)果集)卫漫;
while(c.moveToNext()){將游標(biāo)移到下一行,存在數(shù)據(jù)返回true肾砂,不存在數(shù)據(jù)返回false列赎;
int id=c.getInt(c.getColumnIndex("_id"));獲得對(duì)應(yīng)列名中該行的整型數(shù)據(jù);
String name=c.getString(c.getColumnIndex("name"));獲得對(duì)應(yīng)列名中該行的字符串?dāng)?shù)據(jù);
int age=c.getInt(c.getColumnIndex("age"));獲得對(duì)應(yīng)列名中該行的整型數(shù)據(jù)
}
注意:增刪改使用的是execSQL()方法包吝,查詢用的是rawQuery()方法饼煞。
注:博主博客會(huì)同步發(fā)布到CSDN,歡迎讀者閱讀