SQLite簡介
一赞草、簡介
SQLite是一款輕量級的關系型數(shù)據(jù)庫讹堤,它的運算速度非常快厨疙, 占用資源很少洲守,通常只需要幾百K的內存就足夠了,因而特別適合在移動設備上使用。SQLite不僅支持標準的SQL語法梗醇,還遵循了數(shù)據(jù)庫的ACID事務知允。
SQLite的數(shù)據(jù)庫都是以單個文件的形式存在,這些數(shù)據(jù)都是以B-Tree的數(shù)據(jù)結構形式存儲在磁盤上叙谨。
在事務處理方面温鸽,SQLite通過數(shù)據(jù)庫級上的獨占性和共享鎖來實現(xiàn)獨立事務處理。這意味著多個進程可以在同一時間從同一數(shù)據(jù)庫讀取數(shù)據(jù)手负,但只有一個可以寫入數(shù)據(jù)涤垫。在某個進程或線程想數(shù)據(jù)庫執(zhí)行寫操作之前,必須獲得獨占鎖竟终。在獲得獨占鎖之后蝠猬,其他的讀或寫操作將不會再發(fā)生。
SQLite采用動態(tài)數(shù)據(jù)類型衡楞,當某個值插入到數(shù)據(jù)庫時吱雏,SQLite將會檢查它的類型,如果該類型與關聯(lián)的列不匹配瘾境,SQLite則會嘗試將該值轉換成該列的類型歧杏,如果不能轉換,則該值將作為本身的類型存儲迷守,SQLite稱這為“弱類型”犬绒。但有一個特例,如果是INTEGER PRIMARY KEY兑凿,則其類型不會被轉換凯力,會報一個“datatype missmatch”的錯誤。
概括來講礼华,SQLite支持NULL咐鹤、INTEGER、REAL圣絮、TEXT和BLOB數(shù)據(jù)類型祈惶,分別代表空值、整型值扮匠、浮點值捧请、字符串文本、二進制對象棒搜。
二疹蛉、使用
1.創(chuàng)建數(shù)據(jù)庫
Android為了讓我們能夠更加方便地管理數(shù)據(jù)庫,專門提供了一個SQLiteOpenHelper幫 助類力麸,借助這個類就可以非常簡單地對數(shù)據(jù)庫進行創(chuàng)建和升級可款。
SQLiteOpenHelper是一個抽象類育韩,這意味著如果我們想要使用它的話,就需要創(chuàng)建一個自己的幫助類去繼承它筑舅。SQLiteOpenHelper中有兩個抽象方法座慰,分別是onCreate()和onUpgrade(),這兩個方法分別實現(xiàn)創(chuàng)建翠拣、升級數(shù)據(jù)庫的邏輯版仔。
SQLiteOpenHelper中還有兩個非常重要的實例方法 , getReadableDatabase()和getWritableDatabase()误墓。這兩個方法都可以創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(如果數(shù)據(jù)庫已存在則直接打開蛮粮,否則創(chuàng)建一個新的數(shù)據(jù)庫),并返回一個可對數(shù)據(jù)庫進行讀寫操作的對象谜慌。構建出SQLiteOpenHelper 的實例之后然想,再調用它的getReadableDatabase()或getWritableDatabase()方法就能夠創(chuàng)建數(shù)據(jù)庫了,數(shù)據(jù)庫文件會存放在/data/data/<package name>/databases/目錄下欣范。
SQLite不像其他的數(shù)據(jù)庫擁有眾多繁雜的數(shù)據(jù)類型变泄,它的數(shù)據(jù)類型很簡單,integer表示整型恼琼,real表示浮點型妨蛹,text表示文本類型,blob表示二進制類型晴竞。
2.查看數(shù)據(jù)庫
數(shù)據(jù)庫文件會存放在/data/data/<package name>/databases/目錄下蛙卤。通過adb shell進入到設備控制臺,然后使用cd命令進入到數(shù)據(jù)庫的目錄下噩死,用ls查看該目錄里的文件颤难,可以看到我們定義的數(shù)據(jù)庫文件,數(shù)據(jù)庫文件以".db"結尾已维。
借助SQLite命令來打開數(shù)據(jù)庫行嗤,執(zhí)行鍵入sqlite3 <數(shù)據(jù)庫名稱>即可,例如:
sqlite3 bookstore.db
這時就已經(jīng)打開了bookstore.db數(shù)據(jù)庫垛耳,現(xiàn)在就可以對這個數(shù)據(jù)庫中的表進行管理了昂验。 首先來看一下目前數(shù)據(jù)庫中有哪些表,鍵入.table 命令艾扮。
sqlite> .table
這里還可以通過.schema 命令來查看它們的建表語句。
sqlite > .schema
3.使用SQLiteOpenHelper對數(shù)據(jù)庫進行版本管理
為了實現(xiàn)對數(shù)據(jù)庫版本進行管理,Android系統(tǒng)為我們提供了一個名為SQLiteOpenHelper的抽象類占婉,必須繼承它才能使用泡嘴。SQLiteOpenHelper類提供了兩個重要的方法,分別是onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)逆济,前者用于初次使用軟件時生成數(shù)據(jù)庫表酌予,后者用于升級軟件時更新數(shù)據(jù)庫表結構磺箕。
當調用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()方法獲取用于操作數(shù)據(jù)庫的SQLiteDatabase實例的時候,如果數(shù)據(jù)庫不存在抛虫,android系統(tǒng)會自動生成一個數(shù)據(jù)庫松靡,接著調用onCreate()方法,onCreate()方法在初次生成數(shù)據(jù)庫時才會被調用建椰,在onCreate()方法里可以生成數(shù)據(jù)庫表結構及添加一些應用使用到的初始化數(shù)據(jù)雕欺。
onUpgrade()方法在數(shù)據(jù)庫的版本發(fā)生變化時會被調用,一般在軟件升級時才需改變版本號棉姐,而數(shù)據(jù)庫的版本是由程序員控制的屠列,假設數(shù)據(jù)庫現(xiàn)在的版本是1,由于業(yè)務的變更伞矩,修改了數(shù)據(jù)庫表結構笛洛,這時候就需要升級軟件,升級軟件時希望更新用戶手機里的數(shù)據(jù)庫表結構乃坤,為了實現(xiàn)這一目的苛让,可以把原來的數(shù)據(jù)庫版本設置為2,并且在onUpgrade()方法里面實現(xiàn)表結構的更新湿诊。當軟件的版本升級次數(shù)比較多狱杰,這時在onUpgrade()方法里面可以根據(jù)原版號和目標版本號進行判斷,然后作出相應的表結構及數(shù)據(jù)更新枫吧。
getWritableDatabase()和getReadableDatabase()方法都可以獲取一個用于操作數(shù)據(jù)庫的SQLiteDatabase實例浦旱。但getWritableDatabase() 方法以讀寫方式打開數(shù)據(jù)庫,一旦數(shù)據(jù)庫的磁盤空間滿了九杂,數(shù)據(jù)庫就只能讀而不能寫颁湖。getReadableDatabase()方法先以讀寫方式打開數(shù)據(jù)庫,如果數(shù)據(jù)庫的磁盤空間滿了例隆,就會打開失敗甥捺,當打開失敗后會繼續(xù)嘗試以只讀方式打開數(shù)據(jù)庫。
4.使用SQLiteDatabase操作SQLite數(shù)據(jù)庫
Android提供了一個名為SQLiteDatabase的類镀层,該類封裝了一些操作數(shù)據(jù)庫的API镰禾,使用該類可以完成對數(shù)據(jù)進行添加(Create)、查詢(Retrieve)唱逢、更新(Update)和刪除(Delete)操作(這些操作簡稱為CRUD)吴侦。對SQLiteDatabase的學習,我們應該重點掌握execSQL()和rawQuery()方法坞古。 execSQL()方法可以執(zhí)行insert备韧、delete、update和CREATE TABLE之類有更改行為的SQL語句痪枫; rawQuery()方法用于執(zhí)行select語句织堂。
execSQL()方法的使用例子:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values('炸死特', 4)");
db.close();
在拼接SQL語句時叠艳,要注意一些特殊符號,例如單引號易阳,“&”符號附较,為了保證組拼好的SQL語句語法正確,必須對SQL語句中的這些特殊SQL符號都進行轉義潦俺,顯然拒课,對每條SQL語句都做這樣的處理工作是比較煩瑣的。 SQLiteDatabase類提供了一個重載后的execSQL(String sql, Object[] bindArgs)方法黑竞,使用這個方法可以解決前面提到的問題捕发,因為這個方法支持使用占位符參數(shù)(?)。
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"炸死特", 4});
db.close();
execSQL(String sql, Object[] bindArgs)方法的第一個參數(shù)為SQL語句很魂,第二個參數(shù)為SQL語句中占位符參數(shù)的值扎酷,參數(shù)值在數(shù)組中的順序要和占位符的位置對應。
SQLiteDatabase的rawQuery() 用于執(zhí)行select語句遏匆,使用例子如下:
SQLiteDatabase db = ....;
Cursor cursor = db.rawQuery(“select * from person”, null);
while (cursor.moveToNext()) {
int personid = cursor.getInt(0); //獲取第一列的值,第一列的索引從0開始
String name = cursor.getString(1);//獲取第二列的值
int age = cursor.getInt(2);//獲取第三列的值
}
cursor.close();
db.close();
rawQuery()方法的第一個參數(shù)為select語句法挨;第二個參數(shù)為select語句中占位符參數(shù)的值,如果select語句沒有使用占位符幅聘,該參數(shù)可以設置為null凡纳。帶占位符參數(shù)的select語句使用例子如下:
Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%炸死特%", "4"});
Cursor是結果集游標,用于對結果集進行隨機訪問帝蒿。使用moveToNext()方法可以將游標從當前行移動到下一行荐糜,如果已經(jīng)移過了結果集的最后一行,返回結果為false葛超,否則為true暴氏。另外Cursor還有常用的moveToPrevious()方法(用于將游標從當前行移動到上一行,如果已經(jīng)移過了結果集的第一行绣张,返回值為false答渔,否則為true)、moveToFirst()方法(用于將游標移動到結果集的第一行侥涵,如果結果集為空沼撕,返回值為false,否則為true)和moveToLast()方法(用于將游標移動到結果集的最后一行,如果結果集為空,返回值為false,否則為true ) 。
除了前面介紹的execSQL()和rawQuery()方法托猩,SQLiteDatabase還專門提供了對應于添加、刪除、更新兄世、查詢的操作方法: insert()敬拓、delete()、update()和query()裙戏。這些方法實際上是給那些不太了解SQL語法的人使用的乘凸,對于熟悉SQL語法的程序員而言,直接使用execSQL()和rawQuery()方法執(zhí)行SQL語句就能完成數(shù)據(jù)的添加累榜、刪除营勤、更新、查詢操作壹罚。
insert()方法的使用
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", "炸死特");
values.put("age", 4);
long rowid = db.insert(“person”, null, values);//返回新添記錄的行號葛作,與主鍵id無關
不管第三個參數(shù)是否包含數(shù)據(jù),執(zhí)行Insert()方法必然會添加一條記錄猖凛,如果第三個參數(shù)為空赂蠢,會添加一條除主鍵之外其他字段值為Null的記錄。
delete()方法的使用
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete("person", "personid<?", new String[]{"2"});
db.close();
上面代碼用于從person表中刪除personid小于2的記錄辨泳。
update()方法的使用
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(“name”, “炸死特”);//key為字段名虱岂,value為值
db.update("person", values, "personid=?", new String[]{"1"});
db.close();
上面代碼用于把person表中personid等于1的記錄的name字段的值改為“炸死特”。
query()方法的使用
SQLiteDatabase db = databaseHelper.getWritableDatabase();
Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%炸死特%"}, null, null, "personid desc", "1,2");
while (cursor.moveToNext()) {
int personid = cursor.getInt(0); //獲取第一列的值,第一列的索引從0開始
String name = cursor.getString(1);//獲取第二列的值
int age = cursor.getInt(2);//獲取第三列的值
}
cursor.close();
db.close();
實際上是把select語句拆分成了若干個組成部分菠红,然后作為方法的輸入?yún)?shù)第岖。
query(table, columns, selection, selectionArgs, groupBy, having, orderBy,limit)方法各參數(shù)的含義:
table:表名。相當于select語句from關鍵字后面的部分试溯。如果是多表聯(lián)合查詢蔑滓,可以用逗號將兩個表名分開。
columns:要查詢出來的列名遇绞。相當于select語句select關鍵字后面的部分键袱。
selection:查詢條件子句,相當于select語句where關鍵字后面的部分试读,在條件子句允許使用占位符“?”
selectionArgs:對應于selection語句中占位符的值杠纵,值在數(shù)組中的位置與占位符在語句中的位置必須一致,否則就會有異常钩骇。
groupBy:相當于select語句group by關鍵字后面的部分
having:相當于select語句having關鍵字后面的部分
orderBy:相當于select語句order by關鍵字后面的部分比藻,如:personid desc, age asc;
limit:指定偏移量和獲取的記錄數(shù),相當于select語句limit關鍵字后面的部分倘屹。
5.SQLite數(shù)據(jù)類型
一般的數(shù)據(jù)采用的固定的靜態(tài)數(shù)據(jù)類型银亲,而SQLite采用的是動態(tài)數(shù)據(jù)類型,會根據(jù)存入值自動判斷纽匙。SQLite具有以下五種常用的數(shù)據(jù)類型:
NULL: 這個值為空值
VARCHAR(n):長度不固定且其最大長度為n的字串务蝠,n不能超過4000。
CHAR(n):長度固定為n的字串烛缔,n不能超過254馏段。
INTEGER: 值被標識為整數(shù),依據(jù)值的大小可以依次被存儲為1,2,3,4,5,6,7,8.
REAL: 所有值都是浮動的數(shù)值,被存儲為8字節(jié)的IEEE浮動標記序號.
TEXT: 值為文本字符串,使用數(shù)據(jù)庫編碼存儲(TUTF-8, UTF-16BE or UTF-16-LE).
BLOB: 值是BLOB數(shù)據(jù)塊轩拨,以輸入的數(shù)據(jù)格式進行存儲。如何輸入就如何存儲,不改變格式院喜。
DATA:包含了年份亡蓉、月份、日期喷舀。
TIME: 包含了小時砍濒、分鐘、秒硫麻。
三爸邢、源碼分析
SQLiteOpenHelper是SQLiteDatabase的一個輔助類,用來幫助在應用初始化時創(chuàng)建數(shù)據(jù)庫表以及應用升級時更新數(shù)據(jù)庫表拿愧。下面將從源碼的角度來分析SQLiteOpenHelper的getReadableDatabase()和gegetWritableDatabase()方法杠河。
3.1 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) {
//版本小于1,則會拋出異常赶掖,因為初始創(chuàng)建的數(shù)據(jù)庫版本為0感猛,只有版本大于0時,才會觸發(fā)調用onCreate()方法
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
SQLiteOpenHelper的構造函數(shù)主要是保存參數(shù)傳遞的變量奢赂,為后面創(chuàng)建或打開數(shù)據(jù)庫做準備陪白。
3.2 SQLiteOpenHelper.getReadableDatabase()
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
//如果數(shù)據(jù)庫不為空,并且已經(jīng)打開了膳灶,則直接返回
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
return mDatabase;
}
}
// 如果正在初始化咱士,則拋出異常
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
//writable為false
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {//數(shù)據(jù)庫的名字,則創(chuàng)建一個空的數(shù)據(jù)庫
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) {//調試時信息
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);//打開或者創(chuàng)建數(shù)據(jù)庫
}
} catch (SQLiteException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);//如果以PRIVATE模式打開數(shù)據(jù)時拋出了異常轧钓,則以只讀的方式打開數(shù)據(jù)庫文件
}
}
onConfigure(db);//配置數(shù)據(jù)庫的一些信息序厉,默認實現(xiàn)為空,在onCreate()毕箍、onUpdate()方法之前調用
final int version = db.getVersion();//獲取數(shù)據(jù)庫的版本號弛房,剛創(chuàng)建出來的數(shù)據(jù)庫的版本號為0
if (version != mNewVersion) {//版本號發(fā)生了變化
if (db.isReadOnly()) {//如果是數(shù)據(jù)庫是只讀的,則拋出異常而柑,因為只讀數(shù)據(jù)庫不能被升級
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
db.beginTransaction();//以事務的方式執(zhí)行
try {
if (version == 0) {//初始版本為0文捶,說明是第一次打開或者創(chuàng)建數(shù)據(jù)庫
onCreate(db);//回調onCreate()方法
} else {
if (version > mNewVersion) {//如果新的版本號比舊的版本號小,則把數(shù)據(jù)庫進行降級處理
onDowngrade(db, version, mNewVersion);//降級處理
} else {//如果新的版本號比舊的版本號大媒咳,則把數(shù)據(jù)庫進行升級處理
onUpgrade(db, version, mNewVersion);//升級處理
}
}
db.setVersion(mNewVersion);//設置新的版本號
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
onOpen(db);//在數(shù)據(jù)庫打開之后調用粹排,默認實現(xiàn)為空
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;//保存打開的數(shù)據(jù)庫文件
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}
可以看到,通過getReadableDatabase()方式打開數(shù)據(jù)庫時涩澡,傳遞的writeable參數(shù)為false顽耳,最終調用的是getDatabaseLocked()方法進行打開數(shù)據(jù)庫操作。在getDatabaseLocked()方法中的主要的操作有:
getDatabaseLocked()方法的調用是在獲取獨占鎖synchronized后調用的,因此可以保證每次只有一個線程去操作數(shù)據(jù)庫文件射富。
如果數(shù)據(jù)庫文件已經(jīng)存在了膝迎,并且已經(jīng)打開了,則直接返回該數(shù)據(jù)庫胰耗;
根據(jù)初始化SQLiteOpenHelper時設置的數(shù)據(jù)庫名字弄抬,以PRIVATE模式去打開或者創(chuàng)建一個數(shù)據(jù)庫文件。如果打開數(shù)據(jù)庫文件失敗宪郊,則嘗試以只讀方式打開數(shù)據(jù)庫文件;
數(shù)據(jù)庫連接完成之后拖陆,可以調用onConfigure()方法對數(shù)據(jù)庫進行配置操作弛槐,默認該方法是空實現(xiàn);
-
根據(jù)數(shù)據(jù)庫版本號依啰,決定是調用onCreate()方法還是調用onDowngrade()或者是onUpgrade()方法:
- 如果初始化數(shù)據(jù)庫版本號為0乎串,則表示是第一次打開或者創(chuàng)建數(shù)據(jù)庫,則調用onCreate()方法速警;
- 如果新的數(shù)據(jù)庫版本號大于舊的數(shù)據(jù)庫版本號叹誉,則調用onUpgrade()方法,對數(shù)據(jù)庫進行升級操作闷旧;
- 如果新的數(shù)據(jù)庫版本號小于舊的數(shù)據(jù)庫版本號长豁,則調用onDowngrade()方法,對數(shù)據(jù)庫進行降級操作忙灼;
數(shù)據(jù)庫打開之后匠襟,可以調用onOpen()方法進行一些配置操作,該方法模式是空實現(xiàn)该园;
3.3 SQLiteOpenHelper.getWritableDatabase()
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);//見3.2
}
}
可以看到酸舍,getWritableDatabase()和通過getReadableDatabase()方法最終都是調用getDatabaseLocked()方法,只是傳遞的wirteable參數(shù)不同里初。getReadableDatabase()方法傳遞的writeable參數(shù)為false啃勉,而getWritableDatabase()傳遞的參數(shù)為true。
四双妨、例子
SQLite使用的Demo:SQLiteDemo