版權聲明:本文爲博主原創文章,未經博主容許不得轉載
源碼:github.com/AnliaLee
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論html
博主這兩天心血來潮準備回顧下SQLite的知識,然而網上查找資料的過程是痛苦的,由於不多有一篇博客能把SQLite的入門知識講全的,得好幾篇合着來看才行,所以個人瀏覽器選項卡基本上是這樣的java
正所謂本身動手豐衣足食,我不想之後忘了還要這樣再來一次,因此決定本身從新總結一番,集衆家之長來篇大雜燴。本篇博客內容不少,爲了儘可能涵蓋到全部知識不得已寫得很囉嗦,你們能夠看着目錄按需跳着看,若是有什麼遺漏或寫錯的地方歡迎你們指出來~android
關於SQLite的簡介網上有不少不少,其中看到一篇博客總結得很好,遂直接引用啦~如下摘自Android黃金篇-SQLite數據庫git
SQLite是一款輕量級的關係型數據庫,它的運算速度很是快,佔用資源不多,一般只須要幾百K的內存就足夠了,於是特別適合在移動設備上使用。github
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查看數據庫,這個就看我的喜好了數據庫
在講如何使用SQLite數據庫以前,有必要介紹一下這兩個重要的類:SQLiteDatabase 和 SQLiteOpenHelper,這是SQLite數據庫API中最基礎的兩個類編程
在Android中,SQLite數據庫的使用始於SQLiteDatabase這個類(SQLiteOpenHelper也是基於SQLiteDatabase來進行數據庫建立和版本管理,以後會詳細介紹),咱們能夠看下它的內部方法(未截全)數組
能夠發現insert、query等熟悉的字眼,這些方法都是已經封裝好的,咱們只須要傳入適當的參數便可完成諸如插入、更新、查詢等操做。固然SQLiteDatabase也提供了直接執行SQL語句的方法,如
總結來講,咱們能夠將SQLiteDatabase看做是一個數據庫對象,經過調用其中的方法來建立、刪除、執行SQL命令,以及執行其餘常見的數據庫管理任務。咱們會在以後的章節中詳細講述如何使用SQLiteDatabase一步步搭建咱們的本地數據庫
SQLiteOpenHelper是SQLiteDatabase的輔助類,經過對SQLiteDatabase內部方法的封裝簡化了數據庫建立與版本管理的操做。它是一個抽象類,通常狀況下,咱們須要繼承並重寫這兩個父類方法:
此外父類方法中還有onConfigure、onDowngrade、onOpen,通常項目中不多用到它們,若是你們須要進一步瞭解能夠看下這篇博客,這裏就不贅述了
那麼SQLiteOpenHelper和SQLiteDatabase是如何關聯起來的呢?SQLiteOpenHelper中提供了getWritableDatabase和 getReadableDatabase方法,其最終都調用了getDatabaseLocked,並在第一次調用(或數據庫版本更新)時執行咱們以前在onCreate、onUpgrade等方法中重寫的數據庫操做,這兩個方法的區別在於
通常來講,建立SQLite數據庫的方法有三,咱們按照使用頻率從高到低的順序列出這三種方法
它們最終都是要調用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;
}
複製代碼
解釋一下各個參數
咱們做爲初學者剛入門沒必要過多地深究每一個參數的含義及使用場景,知道有這麼些內容,往後擴展技能起個索引做用就行,所以這裏就不費篇幅繼續深挖了
關於這點頗有必要單獨開個小節囉嗦一下,不少資料都忽略了這個或者講得不是很清楚。數據庫的默認路徑爲
/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表爲例,按順序講講這三種建立數據庫的方法
按照以前講的,咱們須要先繼承SQLiteOpenHelper,而後重寫onCreate與onUpgrade方法
建立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看下結果
先來看下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)");
}
複製代碼
結果同樣的我就不重複貼出來了
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.openOrCreateDatabase與SQLiteDatabase.openOrCreateDatabase本質上沒有太大區別,只是多了一個mode參數用於設置操做模式,可傳入的參數有
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;
}
}
複製代碼
各參數含義以下
像前文所說的,咱們能夠經過SQLiteDatabase.insert和SQLiteDatabase.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();
}
}
複製代碼
咱們先添加幾條數據用來測試
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你們能夠自行查閱資料瞭解,這裏只簡單解釋一下各個參數的含義
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提供query和rawQuery方法執行查詢的操做,查詢結束後返回一個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方法的參數不少,你們簡單瞭解下就行
前文提到執行查詢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早已不是惟一的選擇,咱們利用其打好基礎後,也不妨嘗試下各類熱門的數據庫框架,例如OrmLite、greenDAO、ObjectBox、Realm等等。各個框架都有本身的優勢和不足之處,你們能夠按需選擇適合本身的框架使用。因爲博主並無每一個框架都進行過度析和嘗試,在這強行科普那就是耍流氓了,因此直接拉幾篇寫得很好的對比博客來作外援吧~
相關博客連接
數據庫到底哪家強?
【Android 數據庫框架總結,總有一個適合你!】
Android數據庫框架GreenDao&Realm實戰分析