標(biāo)注:本文為個(gè)人學(xué)習(xí)使用,僅做自己學(xué)習(xí)參考使用,請勿轉(zhuǎn)載和轉(zhuǎn)發(fā)
2018-08-14: 初稿,參考博主coder-pig
0. 引言
- 上一節(jié)主要將的是SQLite的基本操作,本節(jié)學(xué)習(xí)一些稍微高級的東西,數(shù)據(jù)庫事務(wù),怎么將大二進(jìn)制數(shù)據(jù)存儲到數(shù)據(jù)庫中,以及版本升級時(shí)數(shù)據(jù)庫如何處理。
1. SQLite事務(wù)
1.1 什么是SQLite事務(wù)
- 我們都知道數(shù)據(jù)庫是面向多個(gè)用戶的,如果每個(gè)時(shí)刻只有一名用戶在操作數(shù)據(jù)庫,其他用戶需要等待,這個(gè)很影響數(shù)據(jù)庫資源的使用!而多個(gè)人并發(fā)訪問又容易發(fā)生問題,比如A用戶查詢某種表時(shí)并沒有找到X項(xiàng),而在該用戶該沒完成操作之前,另一個(gè)用戶插入了X項(xiàng)而A依舊認(rèn)為沒有X項(xiàng)。
- 舉一個(gè)例子,A給B轉(zhuǎn)100塊,那么A的儲蓄就要減少100塊,B的存款也增加100,兩個(gè)條件需要同時(shí)滿足才能完成交易(事務(wù)),但是假如B賬戶的錢沒有增加,但是A已經(jīng)扣了100塊了,這樣顯然是不可以的,所以A的100塊返還給他,就是恢復(fù)原樣(事件回滾)
- 總結(jié),就是兩個(gè)或多個(gè)操作捆綁到一起的操作,只有所有操作都執(zhí)行了,事務(wù)才算執(zhí)行完畢,才提交。如果又一個(gè)操作沒有執(zhí)行的話,那么事務(wù)就會回滾,就是恢復(fù)原型,之前做的操作都會撤銷!
1.2 舉個(gè)例子
public void transaction() {
SQLiteDatabase db = dbOpenHelper.getWriteableDatabase();
db.beginTransaction(); // 開啟事務(wù)
try {
db.execSQL("update person set amount = amount - 10 where personid = 1");
db.execSQL("update person set amount = amount + 10 where personid = 1");
db.setTransactionSuccessful(); // 設(shè)置事務(wù)標(biāo)志為true,表示提交事務(wù)
} finally {
db.endTransaction(); // 結(jié)束事務(wù)
}
}
- 相關(guān)方法介紹:
beginTransaction():開啟事務(wù)
endTransaction():結(jié)束事務(wù) - 結(jié)束事務(wù)的結(jié)果有兩種,事務(wù)回滾或者提交,而決定回滾還是提交決定與一個(gè)標(biāo)記,默認(rèn)為false,即回滾;
- 而這里使用finally塊是為了避免某個(gè)語句執(zhí)行發(fā)生錯(cuò)誤,導(dǎo)致程序的意外停止,后面結(jié)束事務(wù)的操作沒執(zhí)行。
1.3 總結(jié)
- 簡單的說:寫在事務(wù)里的所有數(shù)據(jù)庫都操作成功,事務(wù)提交,否則,事務(wù)回滾,就是回到前面的狀態(tài),即為執(zhí)行數(shù)據(jù)庫操作的時(shí)候!
- 另外,前面將到,在data/data/<包名>/database/目錄,這個(gè)目錄下有我們創(chuàng)建的db文件外,還有一個(gè)xxx.db-journal這個(gè)文件就是用來讓數(shù)據(jù)庫支持事務(wù)而產(chǎn)生的臨時(shí)的日志文件!
2. SQLite儲存大二進(jìn)制文件
- 一般往數(shù)據(jù)庫中儲存二進(jìn)制文件,比如圖片、音頻、視頻等,對于這些我們一般是儲存文件路徑,但總會有一些奇葩的需求,某天你突然想把這些文件存到數(shù)據(jù)庫里,下面我們以圖片為例子,將圖片保存到SQLite中,以及讀取SQLite中的圖片!
2.1 保存到圖片到SQLite中,讀取SQLite中的圖片
1. 保存圖片到SQLite中:
- 創(chuàng)建數(shù)據(jù)庫表的時(shí)候,需要創(chuàng)建一個(gè)BLOB的字段,用與儲存二進(jìn)制的值
db.execSQL("Create table test (_id INTEGER PRIMARY KEY AUTOINCERMENT, head_img BLOB)");
- 將圖片轉(zhuǎn)換為BLOB格式(這里是以ImageView為例的,如果是普通圖片只是需要轉(zhuǎn)換成Bitmap再調(diào)用即可)
SQLiteDatabase db = mDBService.getWriteDatabase(); // 得到數(shù)據(jù)庫
try {
ByteArrayOutputStream outs = new ByteArrayOutputStream();
((BitmapDrawable) imageview .getDrawable()).getBitmap().compress(
CompressFormat.PNG, 100, outs); // 壓縮為PNG格式,100表示跟原圖大小一樣
Object[] args = new Object[] {outs.toByteArray()};
db.execSQL("INSERT INTO test(head_img) values(?)", args);
outs.close();
db.close();
} catch (Exception e) {
e.printStackTrace();
}
2. 讀取SQLite中的圖片:
SQLiteDatabase db = mDBService.getreadableDatabase();
Cursor cursor = db.rawQuery("SELECT head_img FROM test", null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 取出圖片保存到字節(jié)數(shù)組中
byte[] img = cursor.getBlob(cursor.getColumnIndex("head_img"));
}
}
if (cursor != null) {
cursor.close();
}
// 將圖片顯示到ImageView上
if (img != null) {
ByteArrayInputStream bin = new ByteArrayInputStream(img);
imageview.setImageDrawable(Drawable.createFromStream(bin, "img"));
}
3. SimpleCursorAdapter綁定數(shù)據(jù)庫數(shù)據(jù)
- 嗯,這個(gè)原po主說不太建議使用,盡管用起來簡單!
- 其實(shí)在這將ContentProvider我們就使用過這個(gè)東西來綁定聯(lián)系人列表,這里只是顯示核心代碼,需要的時(shí)候再研究吧,一般采用第三方的框架;
- 常用的框架有:ormlite, greenDao等;
1. 核心代碼
list = findViewById(R.id.mylist);
MyDBOpenHelper mydb = new MyDBOpenHelper(this);
// 查詢數(shù)據(jù)
Cursor cursor = mydb.query("select persionid AS_id, name from person", null);
// 綁定數(shù)據(jù)
SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(this,
R.layout.listitem, cursor, new String[]{"name"}, new int[]{R.id.listtext});
list.setAdapter(simpleCursorAdapter);
2. 代碼解析
SimpleCursorAdapter()的參數(shù)依次為:this,ListView對應(yīng)的列表的布局id,cursor對象,對應(yīng)字段,顯示數(shù)據(jù)的組件
ps:可以顯示多個(gè)數(shù)據(jù),一一對應(yīng)即可,使用SimpleCursorAdapter需要保證數(shù)據(jù)庫表中有名為_id的字段,不然是會報(bào)錯(cuò)的。
4. 數(shù)據(jù)庫升級的一些集錦
- 這一段原po主也沒有項(xiàng)目經(jīng)驗(yàn),也是根據(jù)網(wǎng)上查詢終結(jié)的
- 數(shù)據(jù)庫升級主要調(diào)用onUpgrade()方法
4.1 數(shù)據(jù)庫版本升級,如何升級
- 如果開發(fā)了一款app,然后里面用到了數(shù)據(jù)庫,假定這個(gè)數(shù)據(jù)庫的版本為v1.0,在這個(gè)版本,我們創(chuàng)建了一個(gè)xxx.db的數(shù)據(jù)庫文件,我們通過onCreate()方法創(chuàng)建了第一個(gè)table:t_user, 里面有兩個(gè)字段:_id、user_id;
- 如果后面的數(shù)據(jù)庫的版本中想增加一個(gè)字段:user_name,這個(gè)時(shí)候我們就需要對數(shù)據(jù)庫表的結(jié)構(gòu)進(jìn)行修改了,而我們可以把更新數(shù)據(jù)庫的操作放在onUpgrade()方法中,我們只需要在實(shí)例化自定義SQLiteOpenHelper的時(shí)候,修改版本號,把1改成2這樣,就會自動調(diào)用onUpgrade()方法了!
- 另外,對于每個(gè)數(shù)據(jù)庫版本嗯都應(yīng)該做好相應(yīng)的記錄(文檔),類似下面的表格
| 數(shù)據(jù)庫版本 | android對應(yīng)的版本 | 內(nèi)容 |
|---|---|---|
| v1.0 | 1 | 第一個(gè)版本,包含兩個(gè)字段 |
| v2.0 | 2 | 第二個(gè)版本,新增user_name字段 |
4.2 一些疑問以及解決方案
-
應(yīng)用升級,數(shù)據(jù)庫文件是否會刪除?
不會數(shù)據(jù)什么的都在! -
如果準(zhǔn)備刪除表中的某個(gè)字段或者增加一個(gè)新的字段,原先的數(shù)據(jù)還在么?
原有數(shù)據(jù)還都在 -
還有一種粗暴的更新數(shù)據(jù)庫的版本的方式,不保留數(shù)據(jù)的方式?
下面代碼展示的是第三方的ormlite,也可以自己寫數(shù)據(jù)庫創(chuàng)建以及刪除的代碼
@override
public void onCreate(SQLiteDatabase db, ContentionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, UserEntry.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
@override
public void onUpgrade(SQLiteDatabase db, ContentionSource contentionSource, int arg2m int arg3) {
try {
TableUtils.dropTable(contentionSource, UserEntry.class, true);
onCreate(db, contentSource);
} catch (SQLException e) {
e.printStackTrace();
}
}
-
假如已經(jīng)升級到第三個(gè)版本,我們在第二個(gè)版本增加了一個(gè)表,然后第三個(gè)版本也增加了一個(gè)表,加入用戶直接從第一個(gè)版本升級到第三個(gè)版本,這樣沒經(jīng)過第二個(gè)版本,就沒有增加的那個(gè)表,這種情況的解決方案?
這個(gè)可以在onUpgrade()里寫一個(gè)switch(),結(jié)構(gòu)如下,這個(gè)結(jié)構(gòu)沒有break,這樣是為了保證跨版本升級的時(shí)候,每次數(shù)據(jù)庫丟該都能全部執(zhí)行到,這樣都可以保證結(jié)構(gòu)都是新的!另外不一定是建標(biāo)語句,修改表結(jié)構(gòu)也可以的!
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource,
int arg2, int arg3) {
switch(arg2){
case 1:
db.execSQL(第一個(gè)版本的建表語句);
case 2:
db.execSQL(第二個(gè)版本的建表語句);
case 3:
db.execSQL(第三個(gè)版本的建表語句);
}
}
-
舊表的設(shè)計(jì)太糟糕,很多字節(jié)要改,改動太對,詳見一個(gè)新表,但是要表名一樣,而且以前的一些數(shù)據(jù)要保存到新表中?
解決的思路為:
1.將舊表改名為臨時(shí)表:ALTER TABLE User RENAME TO _temp_User;
2.創(chuàng)建新表:CREATE TABLE User (u_id INTEGER PRIMARY KEY, u_name VARCHAR(20), u_age VARCHAR(4));
3.倒入數(shù)據(jù):INSERT INTO User SELECT u_id, u_name, "18" FROM _temp_User; // 原表中沒有的要自己設(shè)個(gè)默認(rèn)值
4.刪除臨時(shí)表:DROP TABLE _temp_User;