Android 一篇很囉嗦的SQLite知識總結

版權聲明:本文爲博主原創文章,未經博主容許不得轉載
源碼:github.com/AnliaLee
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論html

前言

博主這兩天心血來潮準備回顧下SQLite的知識,然而網上查找資料的過程是痛苦的,由於不多有一篇博客能把SQLite的入門知識講全的,得好幾篇合着來看才行,所以個人瀏覽器選項卡基本上是這樣的java

正所謂本身動手豐衣足食,我不想之後忘了還要這樣再來一次,因此決定本身從新總結一番,集衆家之長來篇大雜燴。本篇博客內容不少,爲了儘可能涵蓋到全部知識不得已寫得很囉嗦,你們能夠看着目錄按需跳着看,若是有什麼遺漏或寫錯的地方歡迎你們指出來~android


什麼是SQLite

關於SQLite的簡介網上有不少不少,其中看到一篇博客總結得很好,遂直接引用啦~如下摘自Android黃金篇-SQLite數據庫git

SQLite介紹

SQLite是一款輕量級的關係型數據庫,它的運算速度很是快,佔用資源不多,一般只須要幾百K的內存就足夠了,於是特別適合在移動設備上使用。github

SQLite特色

  • 輕量級
    使用 SQLite 只須要帶一個動態庫,就能夠享受它的所有功能,並且那個動態庫的尺寸想當小。
  • 獨立性
    SQLite 數據庫的核心引擎不須要依賴第三方軟件,也不須要所謂的「安裝」。
  • 隔離性
    SQLite 數據庫中全部的信息(好比表、視圖、觸發器等)都包含在一個文件夾內,方便管理和維護。
  • 跨平臺
    SQLite 目前支持大部分操做系統,不至電腦操做系統更在衆多的手機系統也是可以運行,好比:Android和IOS。
  • 多語言接口
    SQLite 數據庫支持多語言編程接口。
  • 安全性
    SQLite 數據庫經過數據庫級上的獨佔性和共享鎖來實現獨立事務處理。這意味着多個進程能夠在同一時間從同一數據庫讀取數據,但只能有一個能夠寫入數據。
  • 弱類型的字段
    同一列中的數據能夠是不一樣類型

SQLite數據類型

SQLite具備如下五種經常使用的數據類型:sql

存儲類 存儲類
NULL 值是一個 NULL 值
INTEGER 值是一個帶符號的整數,根據值的大小存儲在 一、二、三、四、6 或 8 字節中
REAL 值是一個浮點值,存儲爲 8 字節的 IEEE 浮點數字
TEXT 值是一個文本字符串,使用數據庫編碼(UTF-八、UTF-16BE 或 UTF-16LE)存儲
BLOB 值是一個 blob 數據,徹底根據它的輸入存儲

調試工具

調試SQLite數據庫推薦使用SQLite Expert(Personal),簡單易用shell

固然你也能夠直接用adb shell查看數據庫,這個就看我的喜好了數據庫


SQLiteDatabase與SQLiteOpenHelper

在講如何使用SQLite數據庫以前,有必要介紹一下這兩個重要的類:SQLiteDatabaseSQLiteOpenHelper,這是SQLite數據庫API中最基礎的兩個類編程

SQLiteDatabase

在Android中,SQLite數據庫的使用始於SQLiteDatabase這個類(SQLiteOpenHelper也是基於SQLiteDatabase來進行數據庫建立和版本管理,以後會詳細介紹),咱們能夠看下它的內部方法(未截全)數組

能夠發現insertquery等熟悉的字眼,這些方法都是已經封裝好的,咱們只須要傳入適當的參數便可完成諸如插入、更新、查詢等操做。固然SQLiteDatabase也提供了直接執行SQL語句的方法,如

  • execSQL
    能夠執行insert、delete、update和CREATE TABLE之類有更改行爲的SQL語句
  • rawQuery
    用於執行select語句

總結來講,咱們能夠將SQLiteDatabase看做是一個數據庫對象,經過調用其中的方法來建立、刪除、執行SQL命令,以及執行其餘常見的數據庫管理任務。咱們會在以後的章節中詳細講述如何使用SQLiteDatabase一步步搭建咱們的本地數據庫

SQLiteOpenHelper

SQLiteOpenHelperSQLiteDatabase的輔助類,經過對SQLiteDatabase內部方法的封裝簡化了數據庫建立與版本管理的操做。它是一個抽象類,通常狀況下,咱們須要繼承並重寫這兩個父類方法:

  • onCreate
    在初次生成數據庫時纔會被調用,咱們通常重寫onCreate生成數據庫表結構並添加一些應用使用到的初始化數據
  • onUpgrade
    當數據庫版本有更新時會調用這個方法,咱們通常會在這執行數據庫更新的操做,例如字段更新、表的增長與刪除等

此外父類方法中還有onConfigureonDowngradeonOpen,通常項目中不多用到它們,若是你們須要進一步瞭解能夠看下這篇博客,這裏就不贅述了

那麼SQLiteOpenHelperSQLiteDatabase是如何關聯起來的呢?SQLiteOpenHelper中提供了getWritableDatabasegetReadableDatabase方法,其最終都調用了getDatabaseLocked,並在第一次調用(或數據庫版本更新)時執行咱們以前在onCreateonUpgrade等方法中重寫的數據庫操做,這兩個方法的區別在於

  • getWritableDatabase
    打開一個可 讀/寫 的數據庫,若是數據庫不存在,則會自動建立一個數據庫,最終返回SQLiteDatabase對象
  • getReadableDatabase
    打開一個可 的數據庫,其餘同上

建立數據庫

一些實操以前要知道的東西

通常來講,建立SQLite數據庫的方法有三,咱們按照使用頻率從高到低的順序列出這三種方法

  • 繼承SQLiteOpenHelper,調用getWritableDatabase / getReadableDatabase打開或建立數據庫(推薦初學者使用)
  • 調用SQLiteDatabase.openOrCreateDatabase打開或建立數據庫
  • 調用Context.openOrCreateDatabase打開或建立數據庫

它們最終都是要調用SQLiteDatabase.openDatabase方法,下圖能夠簡單地歸納它們的調用關係

那麼咱們不妨看下openDatabase幹了啥

public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
	SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
	db.open();//繼續執行打開或建立數據庫的操做
	return db;
}
複製代碼

解釋一下各個參數

  • String path
    數據庫文件路徑
  • CursorFactory factory
    用於構造自定義的Cursor子類對象,在執行查詢操做時返回,若傳入 null 則使用默認的factory構造Cursor
  • int flags
    用於控制數據庫的訪問模式,可傳入的參數有
    • CREATE_IF_NECESSARY:當數據庫不存在時建立該數據庫文件
    • ENABLE_WRITE_AHEAD_LOGGING:繞過數據庫的鎖機制,以多線程操做數據庫的方式進行讀寫
    • NO_LOCALIZED_COLLATORS:打開數據庫時,不根據本地化語言對數據庫進行排序
    • OPEN_READONLY:以只讀方式打開數據庫
    • OPEN_READWRITE:以讀寫方式打開數據庫
  • DatabaseErrorHandler errorHandler
    當檢測到數據庫損壞時進行回調的接口,通常沒有特殊須要傳入 null 便可

咱們做爲初學者剛入門沒必要過多地深究每一個參數的含義及使用場景,知道有這麼些內容,往後擴展技能起個索引做用就行,所以這裏就不費篇幅繼續深挖了

關於建立數據庫的路徑

關於這點頗有必要單獨開個小節囉嗦一下,不少資料都忽略了這個或者講得不是很清楚。數據庫的默認路徑

/data/data/<package_name>/databases/
複製代碼

通常狀況下咱們在建立數據庫時path參數只需傳入「xxx.db」,系統自動會在該默認路徑下建立名爲「xxx.db」的數據庫文件,這樣作最大的好處就是安全,要想拿到這個文件咱們得先root手機(聽說模擬器中能夠直接拿,我沒親自試驗過),並且這個數據庫文件也會隨着App的刪除而刪除。但某些場景下,例如咱們要取出數據庫文件進行調試,在默認路徑下建立數據庫就顯得不那麼方便了。所以,咱們能夠在內部存儲或sd卡中建立該數據庫文件,例如咱們要傳入的path能夠寫成這樣

Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/xxx.db"
複製代碼

那麼數據庫文件xxx.db就會放在內存的SQLiteTest目錄中,須要注意的是,若是SQLiteTest目錄不存在,系統會拋出異常

這是由於咱們前文提到的SQLiteDatabase.openDatabase方法中db.open()後續並無建立目錄的相關代碼,因此咱們須要手動去建立該目錄(記得配置權限)

File dir = new File(Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/");
if (!dir.exists()) {
    dir.mkdir();
}
複製代碼

目錄建立以後咱們就能夠繼續執行建立數據庫的操做了

接下來咱們以建立數據庫文件test.db,建一張test表爲例,按順序講講這三種建立數據庫的方法

1、繼承SQLiteOpenHelper(推薦初學者使用)

按照以前講的,咱們須要先繼承SQLiteOpenHelper,而後重寫onCreateonUpgrade方法

如何調用

建立MySQLiteOpenHelper

public class MySQLiteOpenHelper extends SQLiteOpenHelper {
    public static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
    public static final int DATABASE_VERSION = 1;
    public static final String TABLE_NAME = "test";

    public MySQLiteOpenHelper(Context context, String name){
        this(context, name, null, DATABASE_VERSION);
    }

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        File dir = new File(FILE_DIR);
        if (!dir.exists()) {
            dir.mkdir();
        }
        
        try{
            db.execSQL("create table if not exists " + TABLE_NAME +
                    "(id text primary key,name text)");
        }
        catch(SQLException se){
            se.printStackTrace();
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if(newVersion > oldVersion){
            String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
            db.execSQL(sql);
            onCreate(db);
        }
    }
}
複製代碼

Activity中調用getWritableDatabase / getReadableDatabase(我這裏把動態申請權限的代碼也貼出來,以後就省略了)

public class MainActivity extends AppCompatActivity {
    public static final String DATABASE_NAME = FILE_DIR + "test.db";
    private static final int CODE_PERMISSION_REQUEST = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            //申請寫入權限
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, CODE_PERMISSION_REQUEST);
        } else {
            createDB();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch(requestCode) {
            case CODE_PERMISSION_REQUEST:
                if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    createDB();
                } else{
                }
                break;
            default:
                break;

        }
    }

    private void createDB(){
        MySQLiteOpenHelper sqLiteOpenHelper = new MySQLiteOpenHelper(this,DATABASE_NAME);
        SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();
    }
}
複製代碼

SQLite Expert看下結果

2、調用SQLiteDatabase.openOrCreateDatabase打開或建立數據庫

先來看下openOrCreateDatabase方法的源碼

public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
	return openOrCreateDatabase(file.getPath(), factory);
}

public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
	return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
}

public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory, DatabaseErrorHandler errorHandler) {
	return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
}
複製代碼

能夠看到openOrCreateDatabase其實是以CREATE_IF_NECESSARY模式打開建立數據庫的

如何調用

Activity中執行相應代碼

private static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
public static final String DATABASE_NAME = FILE_DIR + "test.db";

private void createDB(){
	File dir = new File(FILE_DIR);
	if (!dir.exists()) {
		dir.mkdir();
	}
	
	SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(DATABASE_NAME, null);
	database.execSQL("create table if not exists " + "test" +
			"(id text primary key,name text)");
}
複製代碼

結果同樣的我就不重複貼出來了

3、調用Context.openOrCreateDatabase打開或建立數據庫

Context.openOrCreateDatabase的具體實如今ContextImpl類中(關於Context的知識你們能夠自行查閱資料瞭解)

@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
	checkMode(mode);
	File f = getDatabasePath(name);
	int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
	if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
		flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
	}
	if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
		flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
	}
	SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
	setFilePermissionsFromMode(f.getPath(), mode, 0);
	return db;
}
複製代碼

能夠看出Context.openOrCreateDatabaseSQLiteDatabase.openOrCreateDatabase本質上沒有太大區別,只是多了一個mode參數用於設置操做模式,可傳入的參數有

  • MODE_PRIVATE
    默認的模式,建立的數據庫文件只能經過調用該模式的應用程序訪問(或全部應用程序共享相同的用戶ID),咱們通常設置這個參數便可
  • MODE_ENABLE_WRITE_AHEAD_LOGGING
    功能和以前講的設置ENABLE_WRITE_AHEAD_LOGGING同樣
  • MODE_NO_LOCALIZED_COLLATORS
    功能和以前講的設置NO_LOCALIZED_COLLATORS同樣
  • MODE_WORLD_READABLE
    設置後當前文件能夠被其餘應用程序讀取官方不建議在API 17以上的版本設置該參數Android 7.0以上會直接拋出異常:XXX no longer supported)
  • MODE_WORLD_WRITEABLE
    設置後當前文件能夠被其餘應用程序寫入官方不建議在API 17以上的版本設置該參數Android 7.0以上會直接拋出異常:XXX no longer supported)

如何調用

private static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
public static final String DATABASE_NAME = FILE_DIR + "test.db";

private void createDB(){
	File dir = new File(FILE_DIR);
	if (!dir.exists()) {
		dir.mkdir();
	}

	SQLiteDatabase database = this.openOrCreateDatabase(DATABASE_NAME,MODE_PRIVATE,null);
	database.execSQL("create table if not exists " + "test" +
			"(id text primary key,name text)");
}
複製代碼

數據庫的相關操做

這裏只介紹簡單的增刪改查操做,其他的你們能夠自行查閱資料瞭解

SQLiteDatabase提供了insert方法

public long insert(String table, String nullColumnHack, ContentValues values) {
	try {
		return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
	} catch (SQLException e) {
		Log.e(TAG, "Error inserting " + values, e);
		return -1;
	}
}
複製代碼

各參數含義以下

  • String table
    要插入數據的表的名稱
  • String nullColumnHack
    values參數爲空或者裏面沒有內容的時候,執行insert是會失敗(底層數據庫不容許插入一個空行),爲了防止這種狀況,咱們要在這裏指定一個列名,若是發現將要插入的行爲空行時,就會將指定的這個列名的值設爲null,而後再向數據庫中插入。若values不爲null而且元素的個數大於0,則通常將nullColumnHack設爲null
  • ContentValues values
    ContentValues相似一個map.經過鍵值對的形式存儲值

如何調用

像前文所說的,咱們能夠經過SQLiteDatabase.insertSQLiteDatabase.execSQL兩種方式添加數據

ContentValues values = new ContentValues();
values.put("id","1");
values.put("name","name1");
database.insert("test",null,values);

database.execSQL("insert into test(id, name) values(2, 'name2')");
database.close();
複製代碼

結果如圖

一樣的SQLiteDatabase提供delete方法刪除數據

public int delete(String table, String whereClause, String[] whereArgs) {
	acquireReference();
	try {
		SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
				(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
		try {
			return statement.executeUpdateDelete();
		} finally {
			statement.close();
		}
	} finally {
		releaseReference();
	}
}
複製代碼
  • String table
    表名稱
  • String whereClause
    條件語句,至關於where關鍵字,可使用佔位符分隔多個條件
  • String[] whereArgs
    對應條件語句的值的數組,注意如有多個值則需與selection中的多個條件(?符號)一一對應

如何調用

咱們先添加幾條數據用來測試

database.execSQL("insert into test(id, name) values(3, 'name3')");
database.execSQL("insert into test(id, name) values(4, 'name4')");
database.execSQL("insert into test(id, name) values(5, 'name5')");
複製代碼

執行刪除語句

String whereClause = "id=?";
String[] whereArgs = {"3"};
database.delete("test",whereClause,whereArgs);

database.execSQL("delete from test where name = 'name4'");
database.close();
複製代碼

SQLiteDatabase.update用於更新數據

public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
	return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
複製代碼

updateWithOnConflict你們能夠自行查閱資料瞭解,這裏只簡單解釋一下各個參數的含義

  • String table
    表名稱
  • ContentValues values
    ContentValues相似一個map.經過鍵值對的形式存儲值
  • String whereClause
    條件語句,至關於where關鍵字,可使用佔位符分隔多個條件
  • String[] whereArgs
    對應條件語句的值的數組,注意如有多個值則需與selection中的多個條件(?符號)一一對應

如何調用

ContentValues values = new ContentValues();
values.put("name","update2");
String whereClause = "id=?";
String[] whereArgs={"2"};
database.update("test",values,whereClause,whereArgs);

database.execSQL("update test set name = 'update5' where id = 5");
database.close();
複製代碼

SQLiteDatabase提供queryrawQuery方法執行查詢的操做,查詢結束後返回一個Cursor對象。Cursor是一個遊標接口,提供了遍歷查詢結果的方法,因爲本篇博客重點不是這個,就不展開講了。咱們接着看query方法

public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {

	return query(false, table, columns, selection, selectionArgs, groupBy,
			having, orderBy, null /* limit */);
}

public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {

	return query(false, table, columns, selection, selectionArgs, groupBy,
			having, orderBy, limit);
}

public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
	return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
			groupBy, having, orderBy, limit, null);
}

public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
	return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
			groupBy, having, orderBy, limit, cancellationSignal);
}
複製代碼

query方法的參數不少,你們簡單瞭解下就行

  • boolean distinct
    是否去除重複記錄
  • String table
    表名稱
  • String[] columns
    要查詢的列名稱數組,例如這句查詢語句的加黑部分:SELECT id, name FROM test
  • String selection
    條件語句,至關於where關鍵字,可使用佔位符分隔多個條件
  • String[] selectionArgs
    對應條件語句的值的數組,注意如有多個值則需與selection中的多個條件(?符號)一一對應
  • String groupBy
    分組語句,對查詢的結果進行分組
  • String having
    分組條件,對分組的結果進行限制
  • String orderBy
    排序語句
  • String limit
    分頁查詢限制
  • CancellationSignal cancellationSignal
    取消操做的信號,通常用於設置查詢取消時的後續操做,若是沒有則設置爲null。若是操做取消了,query語句運行時會拋出異常(OperationCanceledException)

如何調用

前文提到執行查詢SQL語句的方法是rawQuery,一樣在這裏咱們對照着query方法看看調用過程(若是數據量大的話查詢操做是須要放在單獨的線程中去執行的)

調用query方法,各個參數我就不一一試了,簡單舉幾個例子

if(database!=null){
	Cursor cursor = database.query ("test",null,null,null,null,null,null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
複製代碼

或者

if(database!=null){
	Cursor cursor = database.rawQuery("SELECT * FROM test", null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
複製代碼

加上多個條件限制

if(database!=null){
	String selection = "id=? or name=?";
	String[] selectionArgs = {"1","update2"};
	Cursor cursor = database.query ("test",null,selection,selectionArgs,null,null,null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
複製代碼

或者

if(database!=null){
	Cursor cursor = database.rawQuery("SELECT * FROM test WHERE id=? or name=?", new String[]{"1","update2"});
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
複製代碼


SQLite的替代者們

隨着技術的更迭,原生的SQLite早已不是惟一的選擇,咱們利用其打好基礎後,也不妨嘗試下各類熱門的數據庫框架,例如OrmLitegreenDAOObjectBoxRealm等等。各個框架都有本身的優勢和不足之處,你們能夠按需選擇適合本身的框架使用。因爲博主並無每一個框架都進行過度析和嘗試,在這強行科普那就是耍流氓了,因此直接拉幾篇寫得很好的對比博客來作外援吧~

相關博客連接
數據庫到底哪家強?
【Android 數據庫框架總結,總有一個適合你!】
Android數據庫框架GreenDao&Realm實戰分析

相關文章
相關標籤/搜索