SQLite簡介
結(jié)構(gòu)化查詢語言(Structured Query Language)簡稱SQL,是一種特殊目的的編程語言,專為數(shù)據(jù)庫操作而設(shè)計,用于存取數(shù)據(jù)以及查詢、更新和管理關(guān)系數(shù)據(jù)庫系統(tǒng)。同時,sql也通常是數(shù)據(jù)庫腳本文件的擴展名。
SQLite是一個實現(xiàn)了自給自足的、無服務(wù)器的、零配置的、事務(wù)性的SQL數(shù)據(jù)庫引擎,并且是一個開源項目。Android上自帶SQLite,因此是Android項目上常用的數(shù)據(jù)庫。
對大部分?jǐn)?shù)據(jù)庫來說,大體可以看做是一個較大的Excel文件。數(shù)據(jù)庫中TABLE(表)的概念,對應(yīng)Excel中Sheet;數(shù)據(jù)庫中row(行)、column(列)的概念,對應(yīng)Excel的行(被標(biāo)記為1234的那些)和列(被標(biāo)記為ABCD的那些)的概念。
數(shù)據(jù)庫常用的六個操作,建表、丟表(刪除表)、增、刪、改、查,其SQL語句如下所示。
建表
CREATE TABLE 數(shù)據(jù)表名稱(字段1 類型1(長度),字段2 類型2(長度) …… );
CREATE TABLE IF NOT EXISTS 數(shù)據(jù)表名稱(字段1 類型1(長度),字段2 類型2(長度) …… );
丟表
DROP TABLE 數(shù)據(jù)表名稱;
DROP TABLE IF EXISTS 數(shù)據(jù)表名稱;
增
INSERT INTO 數(shù)據(jù)表 (字段1,字段2,字段3 …) valuess (值1,值2,值3 …);
INSERT INTO 目標(biāo)數(shù)據(jù)表 SELECT * FROM 源數(shù)據(jù)表;
刪
DELETE FROM 數(shù)據(jù)表 WHERE 條件表達式;
DELETE FROM 數(shù)據(jù)表;
改
UPDATE 數(shù)據(jù)表 SET 字段名=字段值 WHERE 條件表達式;
UPDATE 數(shù)據(jù)表 SET 字段1=值1,字段2=值2 …… 字段n=值n WHERE 條件表達式;
查
SELECT * FROM 數(shù)據(jù)表 WHERE 字段名=字段值 ORDER BY 字段名 [DESC];
SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 LIKE '%字段值%' ORDER BY 字段名 [DESC];
SELECT TOP 10 * from 數(shù)據(jù)表 WHERE 字段名 ORDER BY 字段名 [DESC];
SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 IN ('值1','值2','值3');
SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 BETWEEN 值1 AND 值2;
Android常用數(shù)據(jù)庫功能代碼塊
在Android上,如果用Java的方式來使用SQLite,同樣需要對數(shù)據(jù)庫有一定了解。
常用代碼塊示例如下。
建表
建表,由于要定制其各列的字段名及數(shù)據(jù)類型,所以仍然只能使用原始的SQL語句,通過SQLiteDatabase.execSQL(String)
來執(zhí)行。
db.execSQL("CREATE TABLE IF NOT EXISTS "
+ TABLE_NAME + " ( "
+ KEY1 + "類型1(長度)" + ", "
+ KEY2+ "類型2(長度)"
+ " );");
丟表
丟表(此為英文直譯,通常翻譯為刪除表),也是執(zhí)行SQL。
db.execSQL("DROP TABLE IF NOT EXISTS " + TABLE_NAME);
另外,刪除數(shù)據(jù)庫是通過Context.deleteDatabase(String name)
來執(zhí)行。而刪除一個表中的所有數(shù)據(jù),則可以用SQLiteDatabase.delete(String, String, String[])
。
// Delete all data in the table named $TABLE_NAME
db.delete(TABLE_NAME, null, null);
增
數(shù)據(jù)庫的增和刪,都是針對行的。
ContentValues cv = new ContentValues();
cv.put(key1, value1);
cv.put(key2, value2);
SQLiteDatabase db = getWritableDatabase();
db.insert(TABLE_NAME, null, cv);
上面代碼的意義是,給TABLE_NAME
這個表中增加一行。在這一行中,把value1
放到key1
這一列,把value2
放到key2
這一列。
刪
刪除雖然不需要執(zhí)行SQL,但是也要用類似SQL的方式來指定刪除的位置。
String whereClause = key1 + "=?";
String[] whereArgs = new String[]{String.valueOf(value1)};
db.delete(TABLE_NAME, whereClause, whereArgs);
以上代碼中,whereClause
這個位置,填入SQL的WHERE后面的選擇方式;whereArgs
則是替換whereClause
中的問號?
,有幾個問號,這個String數(shù)組中就應(yīng)該給出幾個參數(shù)。
通常是選擇=
作為判斷。上面代碼的意義是,刪除key1
這一列中,值為value1
的所有行。
改
和刪除類似,指定位置要用到類似SQL的方式。SQLiteDatabase
的update()
比delete()
多了一個ContentValues參數(shù),表示那一行需要更新的值。
ContentValues values = new ContentValues();
values.put(key1, value2);
SQLiteDatabase db = getWritableDatabase();
String whereClause = key1 + "=?";
String[] whereArgs = new String[]{String.valueOf(value1)};
db.update(TABLE_NAME, values, whereClause, whereArgs);
以上代碼的意義是,把key1
這一列等于value1
的所有行,其key1
的位置都替換為value2
。
查
這是參數(shù)最繁瑣的一個操作,SQLiteDatabase.query()
一共7個參數(shù),是糟糕的代碼設(shè)計典范。而且部分操作在大部分情況下,都會被設(shè)為null
。
首先拿一個Cursor出來。
SQLiteDatabase db = getWritableDatabase();
// Get all columns
try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
// Deal with the cursor.
}
只傳入表名,其它參數(shù)都給null
,這樣可以把整個表的數(shù)據(jù)都讀出來。
其實這個Cursor并非是一個游標(biāo)一樣的東西,而更像是數(shù)據(jù)庫的一個片段。在數(shù)據(jù)庫比較大時,每次查詢都讀整張表,會造成非常大的IO壓力。因此,若非必要,讀取時只應(yīng)該選取需要的片段。
query()
的參數(shù)如下:
public Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy);
其中,columns
代表需要被選取的列,以字符數(shù)組的形式傳遞;selection
與selectionArgs
,類似于whereClause
和whereArgs
,是在SQL的WHERE關(guān)鍵字后的語句;groupBy
是SQL的GROUP BY后面的語句;having
是SQL的HAVING后面的語句;orderBy
是SQL的ORDER BY后面的語句。
在拿到Cursor后,可以用類似迭代器(Iterator)的方式去使用它。比如,遍歷整個Cursor的某一列的代碼如下。
cursor.moveToFirst();
do {
int column = cursor.getColumnIndex(KEY1);
String value = cursor.getString(column);
// Do sth with the `value`.
} while (cursor.moveToNext());
以上代碼,如果放到之前// Deal with the cursor.
的位置,就可以遍歷整張表。
這里try () {}
是Java v1.7新加入的try with resource用法,會確保cursor.close()
被調(diào)用。
Android相關(guān)類介紹
在Android上使用SQLite,通過SQLiteDatabase.execSQL(String)
接口,可以把SQL的語句轉(zhuǎn)換為字符串,傳入其中執(zhí)行。這樣雖然簡單,但是難以調(diào)試。
另一方面也可以使用它的各種包裝類。由于各種類的層次關(guān)系復(fù)雜,網(wǎng)絡(luò)教程少,這種方法難以使用。
這些功能包裝類基本上都在以下位置:
import android.database.*;
import android.database.sqlite.*;
下面做出一些介紹。
SQLiteOpenHelper
import android.database.sqlite.SQLiteOpenHelper;
這是一個抽象類,需要被繼承后使用。構(gòu)造函數(shù),和兩個Method一定要被override。
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
/**
* Create a helper object to create, open, and/or manage a database.
* The database is not actually created or opened until one of
* {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
*
* <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
* used to handle corruption when sqlite reports database corruption.</p>
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
* corruption, or null to use the default error handler.
*/
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;
}
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
* <p/>
* <p>
* The SQLite ALTER TABLE documentation can be found
* <a >here</a>. If you add new columns
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
* </p><p>
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
在onCreate()
中,通常進行建表(table)的工作;onUpgrade()
中,往往處理一些升級操作,比如要換表(先DROP再CREATE)。這兩個回調(diào)的觸發(fā),與構(gòu)造函數(shù)中傳遞的version相關(guān)。
以下代碼塊是上述兩個回調(diào),加上onDowngrade()
,根據(jù)version不同的調(diào)用情況。另外,onConfigure()
和onOpen()
的回調(diào)情況也在其中。
onConfigure(db);
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
onOpen(db);
通常,對于一個自用的小數(shù)據(jù)庫,在不需要更新表的情況下,version可以萬年不變。但是,為了預(yù)留在未來調(diào)整數(shù)據(jù)庫結(jié)構(gòu)的可操作空間,用SQLiteOpenHelper進行一層封裝總是沒錯。
SQLiteDatabase
import android.database.sqlite.SQLiteDatabase;
SQLiteDatabase是Android對SQLite數(shù)據(jù)庫的包裝類,可以實現(xiàn)其增刪改查的主要功能。其開關(guān),通常由SQLiteOpenHelper來負(fù)責(zé)。
常用接口如下,基本實現(xiàn)了數(shù)據(jù)庫的常用功能。
public long insert(String table, String nullColumnHack, ContentValues values);
public long replace(String table, String nullColumnHack, ContentValues initialValues);
public int delete(String table, String whereClause, String[] whereArgs);
public int update(String table, ContentValues values, String whereClause, String[] whereArgs);
public Cursor rawQuery(String sql, String[] selectionArgs);
public Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit);
public void execSQL(String sql) throws SQLException;
public void execSQL(String sql, Object[] bindArgs) throws SQLException;
public int getVersion();
public void setVersion(int version);
public long getMaximumSize();
public long getPageSize();
public void setPageSize(long numBytes);
public static String findEditTable(String tables);
public SQLiteStatement compileStatement(String sql) throws SQLException;
public boolean isReadOnly();
public boolean isInMemoryDatabase();
public boolean isOpen();
public boolean needUpgrade(int newVersion);
public final String getPath();
public void setLocale(Locale locale);
public long setMaximumSize(long numBytes);
其常用的增刪改查功能,接口實現(xiàn)的內(nèi)部已經(jīng)對引用進行了獲取與釋放,所以通常不需要手動close()
。
DatabaseErrorHandler
這是一個回調(diào)接口,在SQLiteOpenHelper的構(gòu)造函數(shù)中傳入,通過實現(xiàn)void onCorruption(SQLiteDatabase dbObj);
來使用。
/**
* An interface to let apps define an action to take when database corruption is detected.
*/
public interface DatabaseErrorHandler {
/**
* The method invoked when database corruption is detected.
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
* is detected.
*/
void onCorruption(SQLiteDatabase dbObj);
}
通常情況下,不需要使用這個回調(diào),設(shè)置為null
即可。
ContentValues
import android.content.ContentValues;
基本上可以看做是一個HashMap的包裝類,所不同的是,實現(xiàn)了Paracable接口,多了一些Method。
public final class ContentValues implements Parcelable {
public static final String TAG = "ContentValues";
/** Holds the actual values */
private HashMap<String, Object> mValues;
/**
* Creates an empty set of values using the default initial size
*/
public ContentValues() {
// Choosing a default size of 8 based on analysis of typical
// consumption by applications.
mValues = new HashMap<String, Object>(8);
}
// Omitted...
}
Cursor
import android.database.Cursor;
Cursor(游標(biāo))是SQLiteDatabase.query()
后返回的一個對象,也是數(shù)據(jù)庫查詢的一個通用概念。
由于數(shù)據(jù)庫往往是一個二維表格的形式,不能直接給個鍵就返回值,所以在訪問時需要一個中間類進行細微的定位。Cursor會默認(rèn)停留在某一個row(行),同時可訪問這個row的任意一個column(列);但如果要訪問另一個row的某column,只能先換行。move*()
系列操作就是換行用的,而get*()
系列操作則是按行取列用的。
Cursor的主要接口如下:
int getCount();
int getPosition();
boolean move(int offset);
boolean moveToPosition(int position);
boolean moveToFirst();
boolean moveToLast();
boolean moveToNext();
boolean moveToPrevious();
boolean isFirst();
boolean isLast();
boolean isBeforeFirst();
boolean isAfterLast();
int getColumnIndex(String columnName);
int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException;
String getColumnName(int columnIndex);
String[] getColumnNames();
int getColumnCount();
byte[] getBlob(int columnIndex);
String getString(int columnIndex);
void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer);
short getShort(int columnIndex);
int getInt(int columnIndex);
long getLong(int columnIndex);
float getFloat(int columnIndex);
double getDouble(int columnIndex);
int getType(int columnIndex);
boolean isNull(int columnIndex);
void deactivate();
boolean requery();
void close();
boolean isClosed();
void registerContentObserver(ContentObserver observer);
void unregisterContentObserver(ContentObserver observer);
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
void setNotificationUri(ContentResolver cr, Uri uri);
Uri getNotificationUri();
boolean getWantsAllOnMoveCalls();
void setExtras(Bundle extras);
Bundle getExtras();
Bundle respond(Bundle extras);
Cursor的很多接口都可能拋出異常,并且用完后一定要主動調(diào)用close()
,所以通常用try catch finally
、或try with resource包裹其使用操作。
CursorLoader
import android.content.CursorLoader;
Android自3.0開始提供Loader機制,而CursorLoader是其中一例。
CursorLoader實現(xiàn)了異步數(shù)據(jù)庫查詢,避免了大量同步查詢的主線程阻塞。
主要用法是,在構(gòu)造函數(shù)設(shè)置Cursor.query()
用的各種參數(shù),或者在空構(gòu)造函數(shù)后一一設(shè)置,然后用loadInBackground()
來獲取Cursor
。
/* Runs on a worker thread */
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
try {
// Ensure the cursor window is filled.
cursor.getCount();
cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}
DatabaseUtils
import android.database.DatabaseUtils;
這是一個工具類,里面有很多常用的數(shù)據(jù)庫操作,比如Cursor轉(zhuǎn)ContentValues等。
使用前,最好細讀對應(yīng)源碼。