Sqlite和FMDB的詳解,及對模型進行存取

收藏待读

Sqlite和FMDB的詳解,及對模型進行存取

分享是每個優秀的程序員所必備的品質

本文重點說下常用的本地數據庫操作,Sqlite和封裝的FMDB的使用,以及Model的存與取,文章也按此次序寫的。

Sqlite和FMDB的詳解,及對模型進行存取

demo效果.gif

model存取中,以今日頭條為例,清除本地緩存數據後,會延時2s再重新向服務器請求數據

什麼是數據庫

  • 數據庫(Database)是按照數據結構來組織、存儲和管理數據的倉庫
  • 數據庫可以分為2大種類 :關係型數據庫(主流)、對象型數據庫

iOS中的數據存儲方式

  • Plist(NSArrayNSDictionary),只能存儲數組,字典,但是數組和字典裏面不能有自定義對象
  • Preference(偏好設置NSUserDefaults)
  • NSCoding(NSKeyedArchiverNSkeyedUnarchiver)
  • SQLite3
  • Core Data (面對對象)

什麼是SQLite

  • SQLite是一款輕型的嵌入式數據庫
  • 它佔用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了
  • 它的處理速度比Mysql、PostgreSQL這兩款著名的數據庫都還快

調試軟件使用的是Navicat,支持大部分主流數據庫(包括SQLite)

SQL語句的種類

數據定義語句(DDL:Data Definition Language)

  • 包括create和drop等操作
  • 在數據庫中創建新表或刪除表(create table或 drop table)

數據操作語句(DML:Data Manipulation Language)

  • 包括insert(添加)、update(修改)、delete(刪除)等操作

數據查詢語句(DQL:Data Query Language)

  • 可以用於查詢獲得表中的數據
  • 關鍵字select是DQL(也是所有SQL)用得最多的操作
  • 其他DQL常用的關鍵字有where,order by,group by和having

字段類型

SQLite將數據劃分為以下幾種存儲類型:

  • integer : 整型值
  • real : 浮點值
  • text : 文本字符串
  • blob : 二進制數據(比如文件,模型)
    實際上SQLite是無類型的,就算聲明為integer類型,還是能存儲字符串文本(主鍵除外),建表時聲明啥類型或者不聲明類型都可以,也就意味着創表語句可以這麼寫: create table t_student(name, age);
    為了保持良好的編程規範、方便程序員之間的交流,編寫建表語句的時候最好加上每個字段的具體類型

Sqlite使用:

一、創建表

  • create table 表名 (字段名1 字段類型1, 字段名2 字段類型2, …) ;
  • create table if not exists 表名 (字段名1 字段類型1, 字段名2 字段類型2, …)(判斷表是否已存在,不存在則創建) ;

示例: create table t_student (id integer, name text, age inetger, score real) ;

良好的數據庫編程規範應該要保證每條記錄的唯一性,為此,增加了主鍵約束,也就是說,每張表都必須有一個主鍵,用來標識記錄的唯一性,在創表的時候用primary key聲明一個主鍵:

示例: create table t_student (id integer primary key, name text, age integer) ;

主鍵的設計原則:

  • 主鍵應當是對用戶沒有意義的
  • 永遠也不要更新主鍵
  • 主鍵不應包含動態變化的數據
  • 主鍵應當由計算機自動生成

integer類型的id作為t_student表的主鍵。

  • 只要聲明為primary key,就說明是一個主鍵字段
  • 主鍵字段默認就包含了not null 和 unique 兩個約束

如果想要讓主鍵自動增長(必須是integer類型),應該增加autoincrement,

示例: create table t_student (id integer primary key autoincrement, name text, age integer) ;

二、刪表

注意:這個刪是將整個表刪除

  • drop table 表名 ;
  • drop table if exists 表名 ;(判斷表是否存在,存在則刪除)

示例: drop table if exists t_student ;

三、增(插入數據insert)

  • insert into 表名 (字段1, 字段2, …) values (字段1的值, 字段2的值, …) ;

示例: insert into t_student (name, age) values (『小虎牙』, 10) ;
數據庫中的字符串內容應該用單引號 『 』 括住

四、刪(刪除數據delete)

  • delete from 表名 ;

示例: delete from t_student ;

注意:上面的示例會將t_student表中所有記錄都刪掉

五、改(更新數據update)

  • update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, … ;

示例: update t_student set name = 『rc』, age = 18 ;
注意:上面的示例會將t_student表中所有記錄的name都改為jack,age都改為20

六、查(查詢數據select)

  • select 字段1, 字段2, … from 表名 ; (查詢字段1, 字段2數據)
  • select * from 表名;(查詢表中所有的字段)

示例 :

select name, age from t_student ; select * from t_student ;

七、條件語句

如果只想更新或者刪除某些固定的記錄,那就必須在DML語句後加上一些條件

條件語句的常見格式

  • where 字段 = 某個值 ; (不能用兩個 =)
  • where 字段 is 某個值 ; (is 相當於 =)
  • where 字段 != 某個值 ;
  • where 字段 is not 某個值 ; (is not 相當於 !=)
  • where 字段 > 某個值 ;
  • where 字段1 = 某個值 and 字段2 > 某個值 ; (and相當於C語言中的 &&)
  • where 字段1 = 某個值 or 字段2 = 某個值 ; (or 相當於C語言中的 ||)

示例:

  • 將t_student表中年齡大於10 並且 姓名不等於rc的記錄,年齡都改為 5
    update t_student set age = 5 where age > 10 and name != 『rc』 ;
  • 刪除t_student表中年齡小於等於10 或者 年齡大於30的記錄
    delete from t_student where age 30 ;
  • 將t_student表中名字等於rc的記錄,score字段的值 都改為 age字段的值
    update t_student set score = age where name = 『rc』 ;

八、起別名

格式:

  • select 字段1 別名 , 字段2 別名 , … from 表名 別名 ;
  • select 字段1 別名, 字段2 as 別名, … from 表名 as 別名 ;
  • select 別名.字段1, 別名.字段2, … from 表名 別名 ;

示例:

  • 給name起個叫做myname的別名,給age起個叫做myage的別名
    select name myname, age myage from t_student ;
  • 給t_student表起個別名叫做s,利用s來引用表中的字段
    select s.name, s.age from t_student s ;

九、計算記錄的數量

  • select count (字段) from 表名 ;
  • select count ( * ) from 表名 ;
    示例:
  • select count (age) from t_student ;
  • select count ( * ) from t_student where score >= 60;

十、排序

按照某個字段的值,進行排序搜索

select * from t_student order by 字段 ;

示例: select * from t_student order by age ;

默認是按照升序排序(由小到大),也可以變為降序(由大到小)

降序 : select * from t_student order by age desc ;

升序(默認): select * from t_student order by age asc ;

用多個字段進行排序

先按照年齡排序(升序),年齡相等就按照身高排序(降序)

示例: select * from t_student order by age asc, height desc ;

十一、limit

使用limit可以精確地控制查詢結果的數量,比如每次只查詢10條數據

  • select * from 表名 limit 數值1, 數值2 ;

    跳過最前面4條語句,然後取8條記錄

    示例: select * from t_student limit 4, 8 ;

limit常用來做分頁查詢,比如每頁固定顯示5條數據,那麼應該這樣取數據

第1頁:limit 0, 5

第2頁:limit 5, 5

第3頁:limit 10, 5

第n頁:limit 5*(n-1), 5

十二、簡單約束

建表時可以給特定的字段設置一些約束條件,常見的約束有

  • not null :規定字段的值不能為null
  • unique :規定字段的值必須唯一
  • 指定字段的默認值

建議:盡量給字段設定嚴格的約束,以保證數據的規範性

name字段不能為null,並且唯一

age字段不能為null,並且默認為1

示例: create table t_student (id integer, name text not null unique, age integer not null default 1) ;

FMDB使用:

  • FMDB是iOS平台的SQLite數據庫框架
  • FMDB以OC的方式封裝了SQLite的C語言API

優點:

  • 使用起來更加面向對象,省去了很多麻煩、冗餘的C語言代碼
  • 對比蘋果自帶的Core Data框架,更加輕量級和靈活
  • 提供了多線程安全的數據庫操作方法,有效地防止數據混亂

FMDB

FMDB有三個主要的類

  • FMDatabase :其對象就代表一個單獨的SQLite數據庫,用來執行SQL語句
  • FMResultSet :用來執行查詢後的結果集
  • FMDatabaseQueue :用於在多線程中執行多個查詢或更新,它是線程安全的

打開數據庫

通過指定SQLite數據庫文件路徑來創建FMDatabase對象

FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
    NSLog(@"數據庫打開失敗!");
}

文件路徑有三種情況

  • 具體文件路徑 :如果不存在會自動創建
  • 空字符串@”” :會在臨時目錄創建一個空的數據庫,當FMDatabase連接關閉時,數據庫文件也被刪除
  • nil :會創建一個內存中臨時數據庫,當FMDatabase連接關閉時,數據庫會被銷毀

更新數據庫

在FMDB中,除查詢以外的所有操作,都稱為「更新」,create、drop、insert、update、delete等

使用executeUpdate:方法執行更新

- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments

示例 : [db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @18, @"rc"]

執行查詢

- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
// 查詢數據
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
// 遍歷結果集
while ([rs next]) {
    NSString *name = [rs stringForColumn:@"name"];
    int age = [rs intForColumn:@"age"];
    double score = [rs doubleForColumn:@"score"];
}

FMDatabaseQueue

FMDatabase這個類是線程不安全的,如果在多個線程中同時使用一個FMDatabase實例,會造成數據混亂等問題

為了保證線程安全,FMDB提供方便快捷的FMDatabaseQueue類

// FMDatabaseQueue的創建
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
...
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"rc"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    
    FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    while ([rs next]) {
        // …
    }
}];

事務、回滾 :

操作數據庫時,會出現這種情況:更新10條記錄,當更新到第5條時,服務器宕機了,後面的麻煩就來了,我們要每次判斷哪條記錄更新了,哪條記錄沒更新!這時候就用到了事務,將更新10條記錄放到一個事務中,成功完成所有更新操作時再提交,只要其中一條記錄更新失敗就回滾,回到初始狀態,簡單的說就是要麼全部成功,要麼全部不成功!

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"rc"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    
    FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    while ([rs next]) {
        // …
    }
}];

//事務回滾
*rollback = YES;

對模型進行存取

以頭條新聞為例,一條新聞為一條記錄:

1.先在本地數據庫查找是否存在緩存,存在則顯示緩存的新聞,

2.不存在則去頭條服務器請求數據顯示,同時緩存

思路很簡單:

要做到請求了今日頭條的新聞緩存到本地數據庫後,下次打開直接在數據庫取出數據後顯示。

兩種情況:

1、直接緩存後天返回的json數據 ,好處是簡單,避免了模型轉字典的過程,數據原始,但是如果對數據有更新,比如:新聞是否已讀等。

2、將服務器返回的json轉成model(模型)再緩存,缺點就是可能在模型中有為了方便開發而新增的字段,而這些字段是不需要進行緩存的。

第1種比較簡單,就以第2種為例:

寫了一個新聞緩存的工具類

NewsCacheTool.h

/**緩存新聞數據到本地數據庫*/
+ (void)saveNewsToDatabase:(NSArray *)newsArray;
/**讀取新聞(userID對應用戶的id,同個應用可能存在多個賬號登錄情況)*/
+ (NSArray *)selectNewsToDatabase:(NSString *)userID;
/**清除緩存*/
+ (void)clearNewsCache:(void(^)(BOOL success))flag;
NewsCacheTool.m

static FMDatabase *_db;
// 第一次使用就開始創建表
+ (void)initialize{
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"News.sqlite"];
    _db = [FMDatabase databaseWithPath:filePath];
    if ([_db open]) {
        NSLog(@"打開數據庫成功");
        // 自增主鍵、userID、二進制數據流
        NSString *sql = @"create table if not exists t_news (id integer primary key autoincrement,userID text,dict blob);";
        BOOL success = [_db executeUpdate:sql];
        if (success) {
            NSLog(@"創建表成功");
        }else{
            NSLog(@"創建表失敗");
        }
    }else{
        NSLog(@"打開數據庫失敗");
    }
}

// 緩存數據,這裡為了安全起見應該使用事務
+ (void)saveNewsToDatabase:(NSArray *)newsArray{
    // 遍歷模型數組
    for (NewsModel *nesw in newsArray){
        // 用戶的id應該從自己的服務器取得
        NSString *userID = @"001";
        // 這是模型轉字典的,自己用runtime簡單實現了,有很多優秀的第三方庫可以使用,自選
        NSDictionary * newsDic = [nesw getDictionayFromModel];
        NSError *error;
        NSData *data;
        if (@available(iOS 11.0, *)){
            data = [NSKeyedArchiver archivedDataWithRootObject:newsDic requiringSecureCoding:YES error:&error];
        }else{
            data = [NSKeyedArchiver archivedDataWithRootObject:newsDic];
        }
        if (data == nil || error) {
            NSLog(@"緩存失敗:%@", error);
            return;
        }
        BOOL success = [_db executeUpdate:@"insert into t_news (userID,dict) values(?,?)",userID,data];
        if (success) {
            NSLog(@"插入成功");
        }else{
            NSLog(@"插入失敗");
        }
    }
    
}
// 在數據庫中讀取數據
+ (NSArray *)selectNewsToDatabase:(NSString *)userID{
    NSString *sql = [NSString stringWithFormat:@"select * from t_news where userID = '%@';",userID];
    FMResultSet *set = [_db executeQuery:sql];
    NSMutableArray *array = [NSMutableArray array];
    while ([set next]) {
        NSData *data = [set dataForColumn:@"dict"];
        NSError *error;
        NSDictionary *dic;
        if (@available(iOS 11.0, *)) {
            dic = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:&error];
        } else {
            dic = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        }
        if(dic){
            NewsModel *news = [[NewsModel alloc]initWithDictionary:dic];
            [array addObject:news];
        }
    }
    return array;
}

// 清除新聞緩存
+ (void)clearNewsCache:(void (^)(BOOL success))flag{
    BOOL success = [_db executeUpdate:@"delete from t_news;"];
    if(flag){
        flag(success);
    }
}

模型轉字典的過程中,在NewsModel類中,用runtime簡單實現了,實際開發中可能會多層嵌套字典或數據,市面上有很多成熟優秀的輪子,可自行選擇。

- (NSMutableDictionary *)getDictionayFromModel{
    NSMutableDictionary *dicM = [NSMutableDictionary dictionary];
    unsigned int outCount, I;
    // 拷貝屬性列表
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (i = 0; i<outCount; i++) {
        objc_property_t property = properties[I];
        const char *char_p = property_getName(property);
        // 屬性名
        NSString *propertyName = [NSString stringWithUTF8String:char_p];
        // 屬性值
        id propertyValue = [self valueForKey:(NSString *)propertyName];
        // 設置KeyValues
        if (propertyValue) [dicM setObject:propertyValue forKey:propertyName];
    }
    //釋放
    free(properties);
    return dicM;
}

RCSqliteDemo

只要認真看完,在今後的iOS數據庫處理中更定會得心應手!!

原文 : 簡書

相關閱讀

免责声明:本文内容来源于簡書,已注明原文出处和链接,文章观点不代表立场,如若侵犯到您的权益,或涉不实谣言,敬请向我们提出检举。