1. 程式人生 > >關於iOS GYDataCenter本地資料庫解決方案的那些事兒--下卷

關於iOS GYDataCenter本地資料庫解決方案的那些事兒--下卷

之前的部落格講解了如何儲存使用者的敏感資訊和如何使用GYDataCenter建立資料庫和資料表,這一篇主要談一談在開發中經常用的增刪查改解決方案。

3.如何進行增刪查改

先來檢視一下GY的標頭檔案,在GYDataCenter.h裡面只有兩個標頭檔案。 這裡我們就知道了,外部主要使用的就只有GYDataContent和GYModelObject這兩個類的內容,上一次在建立表格的時候就是繼承了GYModelObject,從字面上理解,我們的資料庫操作就應該屬於GYDataContext的內容了。
#import <Foundation/Foundation.h>

#import "GYDBRunner.h"

@protocol GYModelObjectProtocol;

@interface GYDataContext : NSObject

+ (GYDataContext *)sharedInstance;

/**
 *
 * @param modelClass Class of the model object that you want to fetch.
 *	需要查詢的資料型別,在這裡就是需要操作的資料表
 * @param properties Properties you need. Pass nil to get values of all properties.
 *	需要查詢的表格中的欄位,如果傳入空的Array,返回所有的屬性
 * @param primaryKey Primary key value of the model object that you want to fetch.
 *	需要查詢的資料的主鍵
 * @return Object that match the primary key value, or nil if none is found.
 *	返回傳入的表格的物件,物件內持有傳入的欄位的值
 */

- (id)getObject:(Class<GYModelObjectProtocol>)modelClass
     properties:(NSArray *)properties
     primaryKey:(id)primaryKey;

/**
 *
 * @param modelClass Class of the model objects that you want to fetch.
 *	需要查詢的資料型別,在這裡就是需要操作的資料表
 * @param properties Properties you need. Pass nil to get values of all properties.
 *	需要查詢的表格中的欄位,如果傳入空的Array,返回所有的屬性
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	這裡傳入SQL語句,格式為“where........” 這裡是SQL中的條件語句
 * @param arguments Values to bind to the where clause.
 *	上述SQL語句對應的Value
 * @return Objects that match the condition of the where clause.
 *	返回傳入的表格的物件,物件內持有傳入的欄位的值
 */

- (NSArray *)getObjects:(Class<GYModelObjectProtocol>)modelClass
             properties:(NSArray *)properties
                  where:(NSString *)where
              arguments:(NSArray *)arguments;

/** Join two tables.
 *	連標查詢
 * @param leftClass Class of the first join table.
 *	第一個關聯的表格,一般為父表
 * @param leftProperties Properties of leftClass that you need. Pass nil to get values of all properties.
 *	第一個表格的欄位,一般為父表的欄位,如果傳入空的Array,返回所有的屬性
 * @param rightClass Class of the second join table.
 *	關聯的第二個表格,一般為子表
 * @param rightProperties Properties of rightClass that you need. Pass nil to get values of all properties.
 *	關聯的字表的欄位,一般為字表的欄位, 如果傳入空的Array,返回所有的屬性
 * @param joinType GYSQLJoinTypeInner, GYSQLJoinTypeLeft or GYSQLJoinTypeCross.
 *	兩個表關聯的方式
 * @param joinCondition Join condition. For example: 'leftTableName.property1 = rightTableName.property2'.
 *	關聯的欄位
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	這裡傳入SQL語句,格式為“where........” 這裡是SQL中的條件語句
 * @param arguments Values to bind to the where clause.
 *	上述SQL語句對應的Value
 * @return @[ @[`Objects of left class`], @[`objects of right class`] ].
 *	返回傳入的表格的物件的陣列,物件內持有傳入的欄位的值
 */

- (NSArray *)getObjects:(Class<GYModelObjectProtocol>)leftClass
             properties:(NSArray *)leftProperties
                objects:(Class<GYModelObjectProtocol>)rightClass
             properties:(NSArray *)rightProperties
               joinType:(GYSQLJoinType)joinType
          joinCondition:(NSString *)joinCondition
                  where:(NSString *)where
              arguments:(NSArray *)arguments;

/**
 *
 * @param modelClass Class of the model objects that you want to query.
 *	需要查詢的資料型別,在這裡就是需要操作的資料表
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	這裡傳入SQL語句,格式為“where........” 這裡是SQL中的條件語句
 * @param arguments Values to bind to the where clause.
 *	上述SQL語句對應的Value
 * @return Primary key values that match the condition of the where clause.
 *	返回一組表物件
 */

- (NSArray *)getIds:(Class<GYModelObjectProtocol>)modelClass
              where:(NSString *)where
          arguments:(NSArray *)arguments;

/**
 *
 * @param modelClass Class of the model objects that you want to query.
 *	需要查詢的資料型別,在這裡就是需要操作的資料表
 * @param function Aggregate function. For example: 'count(*)', 'sum(value)'...
 *	用於來統計
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	這裡傳入SQL語句,格式為“where........” 這裡是SQL中的條件語句
 * @param arguments Values to bind to the where clause.
 *	上述SQL語句對應的Value
 * @return Result of the aggregate function.
 *	返回統計結果
 */

- (NSNumber *)aggregate:(Class<GYModelObjectProtocol>)modelClass
               function:(NSString *)function
                  where:(NSString *)where
              arguments:(NSArray *)arguments;

/**
 *
 * @param object The object to be saved.
 *	儲存表資料
 */

- (void)saveObject:(id<GYModelObjectProtocol>)object;

/**
 *清除某張表的所有資料
 *
 */
- (void)deleteObject:(Class<GYModelObjectProtocol>)modelClass
          primaryKey:(id)primartyKey;

/**
 *
 * @param modelClass Class of the model objects that you want to delete.
 *	需要刪除資料表格
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	需要刪除的條件
 * @param arguments Values to bind to the where clause.
 *	需要刪除條件的值
 */

- (void)deleteObjects:(Class<GYModelObjectProtocol>)modelClass
                where:(NSString *)where
            arguments:(NSArray *)arguments;

/**
 *
 * @param modelClass Class of the model object that you want to update.
 *	需要更新的表
 * @param set Property and new value pairs.
 *	需要更新的內容,這裡傳入一個字典
 * @param primaryKey Primary key value of the model object that you want to update.
 *	主鍵
 */

- (void)updateObject:(Class<GYModelObjectProtocol>)modelClass
                 set:(NSDictionary *)set
          primaryKey:(id)primaryKey;

/**
 *
 * @param modelClass Class of the model object that you want to update.
 *	需要更新的表
 * @param set Property and new value pairs.
 *	需要更新的內容,這裡傳入一個字典
 * @param primaryKey Primary key value of the model object that you want to update.
 *	主鍵
 * @return A new updated object.
 *	返回更新後的物件
 */

- (id)updateAndReturnObject:(Class<GYModelObjectProtocol>)modelClass
                        set:(NSDictionary *)set
                 primaryKey:(id)primaryKey;

/**
 *
 * @param modelClass Class of the model objects that you want to update.
 *	需要更新的表
 * @param set Property and new value pairs.
 *	需要更新的內容,這裡傳入一個字典
 * @param where Where clause of SQL. Use '?'s as placeholders for arguments.
 *	需要更新的SQL,這裡是條件語句
 * @param arguments Values to bind to the where clause.
 *	SQL語句對應的值
 */

- (void)updateObjects:(Class<GYModelObjectProtocol>)modelClass
                  set:(NSDictionary *)set
                where:(NSString *)where
            arguments:(NSArray *)arguments;

//建立一個數據庫事務
- (void)inTransaction:(dispatch_block_t)block
               dbName:(NSString *)dbName;

- (void)vacuumAllDBs;

- (void)synchronizeAllData;

@end

現在上面的方法進行逐一講解: 這裡我們需要理解的一點,我們不需要直接呼叫上面的方法,在我們建立資料表的時候,那一個表模型就封裝了一層,直接呼叫模型表的方法就行了。

a.增刪查改--查,查詢在這個資料庫中是很重要的。

/*
 @method queryAllContactsInfos
 @abstrac 查詢所有聯絡人資訊
 @discussion 查詢所有聯絡人資訊
 @param No param
 @result @[QYJContactsInfo, ...]
 */
+ (NSArray *)queryAllContactsInfos {
    NSMutableArray *results = nil;
    NSString *where = @"";//這裡查詢所有的標中的資料,不需要額外的條件,故傳null
    NSArray *arguments = nil;//上面SQL是NULL,所以這裡傳入一個nil,sql的中的'?'佔位符與陣列的個數對應
    
    
    /**
     * 此處如果需要對查獲的資料進行出庫的時候的排序可以使用ORDER BY (order by)
     * ASC 為升序, DESC 為降序, 不寫預設為升序,不使用order by按自增ID排序輸出
     * e.g:
     *    NSString *where = @"ORDER BY name";//按名字升序
     *    NSString *where = @"ORDER BY name, phoneNum DESC"//先按名字升序,再按電話號碼降序
     *    NSString *where = @"ORDER BY name DESC, phoneNum"//先按名字降序,再按電話號碼升序
     *    其他查詢的寫法和SQL語句一致
     *    "where name = ? ORDER BY nameFirstLetter"
     *    "where name = ? AND phoneNum ORDER BY name";
     */
    results = [QYJContactsInfo objectsWhere:where arguments:arguments].mutableCopy;
    return results;
}

/*
 @method queryContactsInfoByName:
 @abstrac 根據姓名去查詢聯絡人
 @discussion 根據姓名去查詢聯絡人
 @param name NSString
 @result @[QYJContactsInfo, ...]
 */
+ (NSArray *)queryContactsInfoByName:(NSString *)name {
    //手機聯絡人可能存在同名的
    
    NSMutableArray *results = nil;
    /**
     * 如果需要多個查詢的條件用AND來連線
     * e.g: 根據姓名和號碼查詢
     *     NSString *where = @"WHERE name = ? AND phoneNum = ?";
     *     NSArray *arguments = @[name, phoneNum];
     *
     * 如果需要模糊查詢用%%來表示,這OC中%需要轉譯
     * e.g:
     *    NSString *where = @"WHERE name LIKE '%%?%%'";
     *    NSArray *arguments = @[name];
     *    OR
     *    NSString *where = [NSString stringWithFormat:@"WHERE name LIKE '%%%@%%'", name];
     *
     * 如果需要滿足任意條件的資料使用OR來連線
     * e.g:
     *    NSString *where = @"WHERE name = ? OR phoneNum = ?"
     *    NSArray *arguments = @[name, phoneNum];
     */
    NSString *where = @"WHERE name = ?";
    NSArray *arguments = @[name];
    
    results = [QYJContactsInfo objectsWhere:where arguments:arguments].mutableCopy;
    return results;
}

b.增刪查改--改,改就是更新,這裡的原則是能在庫裡面修改的資料,就不要拿出資料庫來。

/*
 @method updateContactsInfoByName:set:
 @abstrac 按名字修改對應的欄位的資料
 @discussion 按名字修改對應的欄位的資料
 @param name NSString set NSDictionary
 @result No result
 */
+ (void)updateContactsInfoByName:(NSString *)name set:(NSDictionary *)set {
    /**
     * 假定 name為Avalanching, set 是 @{@"phoneNum":@"11111111"};
     * 將資料表中name為Avalanching 的 phoneNum 的值改為 "11111111"
     */
    NSString *where = @"WHERE name = ?";
    NSArray *arguments = @[name];
    
    //不會返回更新後的資料
    [QYJContactsInfo updateObjectsSet:set Where:where arguments:arguments];
}

/*
 @method updateContactsInfoByInfo:set:
 @abstrac 將需要修改的QYJContactsInfo物件和修改的資料傳入,更新表資料
 @discussion 將需要修改的QYJContactsInfo物件和修改的資料傳入,更新表資料
 @param info QYJContactsInfo set NSDictionary
 @result No result
 */
+ (void)updateContactsInfoByInfo:(QYJContactsInfo *)info set:(NSDictionary *)set {
    //這裡直接用到GYDataContext的方法,要引入GYDataContext.h
    
    //這裡是更新QYJContactsInfo中 primaryKeyId 等於 info.primaryKeyId 的資料
    [[GYDataContext sharedInstance] updateObject:[info class] set:set primaryKey:@(info.primaryKeyId)];
    
    //這裡是更新QYJContactsInfo中 primaryKeyId 等於 info.primaryKeyId 的資料 並返回新的物件
    QYJContactsInfo *newinfo = [[GYDataContext sharedInstance] updateAndReturnObject:[info class] set:set primaryKey:@(info.primaryKeyId)];
    
    
    //將表中滿足條件的資料全部更新成set中的數值
    NSString *where = @"WHERE name = ?";
    NSArray *arguments = @[info.name];
    [[GYDataContext sharedInstance] updateObjects:[info class] set:set where:where arguments:arguments];
}

c.增刪查改--刪,刪除(邏輯刪除和物理刪除)

這裡首先要明確,邏輯刪除不是刪除,僅僅是將資料庫中的資料狀態改變一下,邏輯刪除根據資料庫結構來定的,這裡不做講解;物理刪除就將資料徹底從資料庫中移除。
/*
 @method cleanContactsInfos
 @abstrac 清空通訊錄表格裡面所有的資料
 @discussion 清空通訊錄表格裡面所有的資料
 @param No param
 @result No result
 */
+ (void)cleanContactsInfos {
    
    NSString *where = @""; //清除所有資料,不填寫任何條件
    NSArray *arguments = nil;
    /**
     * 按條件刪除
     * where = @"WHERE name = ?";
     * argument = @[name];
     */
    [[GYDataContext sharedInstance] deleteObjects:[QYJContactsInfo class] where:where arguments:arguments];
    
    //根據primaryKeyId去刪除資料
    [[GYDataContext sharedInstance] deleteObject:[QYJContactsInfo class] primaryKey:@(1)];
}

/*
 @method deleteContactsInfosByInfo:
 @abstrac 刪除某一條資料
 @discussion 刪除資料
 @param info QYJContactsInfo
 @result No result
 */
+ (void)deleteContactsInfosByInfo:(QYJContactsInfo *)info {
    [info deleteObject];
}

d.其他,開發中經常用到一些問題。
1.特殊字元的轉譯查詢的問題,'%', ' ' ' , '/'等等。
/*
 @method queryContactsInfoBySpecialCharacter:
 @abstrac 查詢包含特殊字元的資料 (_, %, /, \, *)
 @discussion 查詢包含特殊字元的資料 (_, %, /, \, *)
 @param character 特殊字元
 @result NSArray<QYJContactsInfo *> *
 */
+ (NSArray<QYJContactsInfo *> *)queryContactsInfoBySpecialCharacter:(NSString *)character {
    
    //使用escape關鍵字和[]來轉譯查詢 /, *, %這一類的關鍵字
    //NSString *where = [NSString stringWithFormat:@"WHERE name LIKE '[%@]'", character];
    NSString *where = [NSString stringWithFormat:@"WHERE name LIKE '%%*%@%%' escape'*' ORDER BY namePinyin", character];
    NSArray *arguments = nil;
    
    return [QYJContactsInfo objectsWhere:where arguments:arguments];
}
2.連結串列查詢
/*
 @method associationQuery
 @abstrac 關聯查詢
 @discussion 關聯查詢
 @param No param
 @result No result
 */
+ (NSArray *)associationQuery {
    
    NSString *where = @"";
    NSArray *arguments = @[];
    //這裡只建立了一張表,所以兩個表都寫為[QYJContactsInfo class], 實際開發是兩張不同的表格
    NSArray *result = [[GYDataContext sharedInstance] getObjects:[QYJContactsInfo class]
                                    properties:nil
                                       objects:[QYJContactsInfo class]
                                    properties:nil
                                      joinType:GYSQLJoinTypeInner
                                 joinCondition:@"這裡是相關聯的欄位"
                                         where:where
                                     arguments:arguments];
    
    //如果不能理解這查詢的方式,可以先查一張表,再把查處來的表格,再查另外一張表
    
    return result;
}
3.索引表的快速刪除
#define Local_Tabel @"IndexesTabel"
#define dbPath @"這裡是資料庫的路徑"
#define local_dbQueue [FMDatabaseQueue databaseQueueWithPath:dbPath]

+ (void)updateIndexesTabelByCondition:(NSString *)condition {
    //這裡需要直接寫SQL,所以這裡需要用底層的FMDB
    // 一般索引表都是存一個id, ids 欄位 對應的只為"1, 2, 3, 4, 5, 6";
    // id 為4 的資料已經從主表中刪除了,現在要修改ids含有4的資料,將4去除
    // 才用replace關鍵字去替代,一次替代之後有可能出現的情況是
    // "1, 2, 3, 4, 5" =(第一次replace)=> "1, 2, 3, ,5"
    // "4, 5, 6" =(第一次replace)=> ",5,6"
    // "1, 2, 3, 4" =(第一次replace)=> "1, 2, 3,"
    //後面兩條SQL 用於修改正 資料庫裡面的結構
    
    NSString * sql = [NSString stringWithFormat:@"UPDATE %@ SET schedule_id = REPLACE(schedule_id, ?, '')", Local_Tabel];
    
    NSString * sqlDetele = [NSString stringWithFormat:@"DELETE FROM %@ WHERE schedule_id = ','", Local_Tabel];
    
    NSString * sqlRepace = [NSString stringWithFormat:@"UPDATE %@ SET schedule_id = REPLACE(schedule_id, ',,', ',')", Local_Tabel];
    
    [local_dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:sql, condition];
        [db executeUpdate:sqlRepace];
        [db executeUpdate:sqlDetele];
    }];
}

4.資料遷移 這裡不提供具體程式碼,只提供思路,最簡單的做法就是從將舊的資料從資料庫中取出來,然後組建成新的資料格式存入新的表格,這裡我們上次提到了一個重寫  - ( BOOL )isEqual:( id )object 的方法,這裡可以通過NSArray轉NSSet的方式過濾掉重複的資料,具體判斷相等的規則就又開發者根據具體的情況去定了。    一般常規開發中升級資料庫,需要將舊的表格的資料取出來,然後建立新表格去存放,小版本的升級就要避免遷移的時候修改很多,這裡就要讓舊錶和新表名字保持一致,將操作放到一個事務裡面去處理。

  這些就是簡單的GYDataCenter資料庫使用了。