全部產品
Search
文件中心

Mobile Platform as a Service:DAO 儲存

更新時間:Jul 13, 2024

普通 KV 儲存只能儲存單一資料型別或封裝好的 OC 對象,且不支援搜尋。當業務有 sqlite 訪問需求時,可由統一儲存的 DAO 功能進行簡化和封裝。基本工作原理如下:

DAO

  1. 定義一個 xml 格式的設定檔,描述各 sqlite 操作的函數、返回的資料類型、需要加密的欄位等。

  2. 定義一個 DAO 對象的介面 DAOInterface(@protocol),介面方法名、參數與設定檔裡的描述一致。

  3. 業務將 xml 設定檔傳給 APDataCenterdaoWithPath 方法,產生 DAO 訪問對象。該對象直接強轉為 id<DAOInterface>

  4. 接下來業務就可以直接調用 DAO 對象的方法,統一儲存會將該方法轉換為設定檔裡描述的資料庫操作。

樣本

  • 設定檔第一行定義了預設表名和資料庫版本,以及初始化方法。

  • insertItemgetItem 是插入和讀取資料的兩個方法,接收參數並格式化到 SQL 運算式裡。

  • createTable 方法會在底層被預設調用一次。

    <module name="Demo" initializer="createTable" tableName="demoTable" version="1.0">
    <update id="createTable">
      create table if not exists ${T} (index integer primary key, content text)
    </update>
    
    <insert id="insertItem" arguments="content">
      insert into ${T} (content) values(#{content})
    </insert>
    
    <select id="getItem" arguments="index" result="string">
      select * from ${T} where index = #{index}
    </select>
    </module>
  • DAO 介面定義:

    @protocol DemoProtocol <APDAOProtocol>
    - (APDAOResult*)insertItem:(NSString*)content;
    - (NSString*)getItem:(NSNumber*)index;
    @end
  • 建立 DAO 代理對象,假設設定檔名為 demo_config.xml,在 Main Bundle 中。

  • insertItem 方法寫入一個資料,擷取它的索引,再用該索引把寫入的資料讀出來。

    NSString* filePath = [[NSBundle mainBundle] pathForResource:@"demo_config" ofType:@"xml"];
    id<DemoProtocol> proxy = [[APDataCenter defaultDataCenter] daoWithPath:filePath userDependent:YES];
    [proxy insertItem:@"something"];
    long long lastIndex = [proxy lastInsertRowId];
    NSString* content = [proxy getItem:[NSNumber numberWithInteger:lastIndex]];
    NSLog(@"content %@", content);
  • 其中 lastInsertRowIdAPDAOProtocol 的一個方法,用來取最後插入的行的 rowId。想讓 DAO 對象支援該方法,只要在聲明 DemoProtocol 時繼承自 APDAOProtocol 即可。

關鍵字

module

<module name="demo" initializer="createTable" tableName="tableDemo" version="1.5" resetOnUpgrade="true" upgradeMinVersion="1.2">
  • initializer 參數可選。對於 initializer 指定的 update 方法,DAO 認為是資料庫建表方法,會在第一次 DAO 請求時預設執行一次。

  • tableName 指定下面方法裡預設操作的表名,在 SQL 陳述式裡可以用 ${T}或${t} 代替,不用每次都寫表名。建議每個設定檔只操作一張表。tableName 可空,應對同一設定檔操作相同格式的多張表的情況。比如聊天訊息分表處理,可以調用 DAO 對象的 setTableName 方法設定需要操作的表名。

  • version 是設定檔的版本號碼,格式為 x.x,建立 table 後,會以 tableName 作為 key,把表的版本存到資料庫檔案的 TableVersions 表裡,配合 upgrade 塊進行表的更新。

  • resetOnUpgrade,如果為 TRUE 或 YES,當 version 更新後,會刪除原表,而不是調用 ungrade 塊。無此參數為預設為 false。

  • upgradeMinVersion,如果不為空白,對於小於這個版本的資料庫檔案,直接重設,否則執行升級操作。

const

<const table_columns="(id, time, content, uin, read)"/>
  • 定義一個字串類型的常量,table_columns 是常量的名字,等號(=)後方為常量值,在設定檔裡可以使用 ${常量名} 來引用。

select

<select id="find" arguments="id, time" result=":messageModelMap">
  select * from ${T} where id = #{id} and time > @{time}
</select>

@arguments

  • 參數名的列表,用 , 分隔。調用者傳進來的參數依賴 arguments 裡的描述依次命名。DAO 對象的 selector 調用時不會攜帶參數名,所以必須在這裡按順序命名。

  • 參數名前面有 $ 符號時,表示這個參數不接受 nil 值。業務的調用 DAO 介面,是允許傳 nil 參數的,如果某個參數前面有 $ 符號,當調用者不小心傳入了 nil 值,DAO 調用會自動失敗。防止發生不可預知的問題。

  • 關於如何引用參數,參見下文中的 引用方式

例如,在上方的代碼中,對應的 selector 為:

- (MessageModel*)find:(NSNumber*)id time:(NSNumber*)time;

如果 DAO 對象調用 [daoProxy find:@1234 time:@2014],那麼拼接好後的 SQL 陳述式是:

select * from tableDemo where id = ? and time > 2014

並且 @1234 這個 NSNumber 會交給 sqlite 綁定。

@result

result 裡可以填寫 DAO 方法的傳回值,用 [] 括起來表示返回數群組類型,會對 select 執行的返回一直進行迭代,直到 sqlite 無結果返回。如果不用 [] 括起來,表示只返回一個結果,對 select 執行的返回只迭代一次,類似 FMDB 庫裡 FMResultSet 只調用一次 next 方法。

傳回型別:

  • int:只有一個結果,返回 [NSNumber numberWithInt] 類型,注意溢出的可能。

  • long long:只有一個結果,返回 [NSNumber numberWithLongLong] 類型。

  • bool:只有一個結果,返回 [NSNumber numberWithBool] 類型。

  • double:只有一個結果,返回 [NSNumber numberWithDouble] 類型。

  • string:只有一個結果,返回 NSString* 類型。

  • binary:只有一個結果,返回 NSData* 類型。

  • [int]:返回數組,數組裡為[NSNumber numberWithInt]

  • [long long]:返回數組,數組裡為 [NSNumber numberWithLongLong]

  • [bool]:返回數組,數組裡為 [NSNumber numberWithBool]

  • [double]:返回數組,數組裡為 [NSNumber numberWithDouble]

  • [string]:返回數組,數組裡為 NSString*

  • [binary]:返回數組,數組裡為 NSData*

  • [{}]:返回數組,數組裡是列名 > 列值的 map。

  • [AType]:返回數組,數組裡是填好的自訂類。

  • {}:只有一個結果,返回列名 > 列值的 map。

  • AType:只有一個結果,返回填好的自訂類。

  • [:AMap]:返回數組,數組裡是使用 xml 裡定義的 AMap 映射出來的對象。

  • :AMap:只有一個結果,使用設定檔裡定義的 AMap 來描述對象。

例如,上面的例子中,傳回型別為 :messageModelMap。具體返回的 Objective-C 類型,以及需要特殊映射的列都會在 messageModelMap 裡定義。參考 map 關鍵字。

@foreach:select 也支援 foreach 欄位,用法下文介紹的 insertupdatedelete 裡的相似。不同的是,select 方法如果指定了 foreach 參數,那麼會執行 N 次 SQL 的 select 操作,並把結果放到數組裡返回。所以如果 DAO 的 select 方法是 foreach 的,它的傳回值在 protocol 裡一定要定義成 NSArray*

insert/update/delete

<insert id="addMessages" arguments="messages" foreach="messages.model">
    insert or replace into ${T} (id, content, uin, read) values(#{model.msgId}, #{model.content}, #{model.uin}, #{model.read})
</insert>

<update id="createTable">
  <step>
    create table if not exists ${T} (id integer primary key, content text, uin integer, read boolean)
  </step>
  <step>
    create index if not exists uin_idx on ${T} (uin)
  </step>
</update>

<delete id="remove" arguments="msgId">
  delete from ${T} where msgId = #{msgId}
</delete>
  • insert、update、delete 方法格式相同,參數的拼接與引用和 select 方法相同。

  • insert 和 delete 關鍵字是為了更好的區分方法用途。你完全可以把一個 delete from table 的操作寫在 update 函數中。

  • 在 DAO 介面中,需要執行 insert、update 或 delete 的方法,傳回值為 APDAOResult*,標識執行是否成功。

@foreach

  • 當 insert、update、delete 方法後面有 foreach 欄位時,這個方法被調用時,會依次對參數數組裡的每個元素執行一次 SQL。

  • 要求 foreach 欄位遵循格式: collectionName.item。其中 collectionName 必須對應 arguments 裡的一個參數,指定迴圈的容器物件,並且調用時必須為一個 NSArray 或 NSSet 類型; item 表示從容器裡取出的對象,用作迴圈變數。不能和 arguments 裡的任何參數重名,這個 item 可以當作一個普通參數用在 SQL 陳述式裡。

例如代理方法為:

- (void)addMessages:(NSArray*)messages;

messages 為 MessageModel 數組,那麼對於 messages 裡的每個 model,都會執行一次 SQL 調用,這樣就能實現把數組裡的元素一次性插入資料庫而上層不需要關心迴圈的調用。底層會把這次操作合并為一個事務而提升效率。

step

<upgrade toVersion='3.2'>
    <step resumable="true">alter table ${T} add column1 text</step>
    <step resumable="true">alter table ${T} add column2 text</step>
</upgrade>
  • 在 insert, update, delete, upgrade 方法裡,可能遇到這種情況:執行一個 DAO 方法,但是需要調用多次 SQL update 操作執行多條 SQL 陳述式。比如使用者建表後,又要建立一些索引。在函數裡

    包裹的語句,都會當作一次 SQL update 被單獨執行,底層會把所有操作合并為一次事務。比如上面的 createTable 方法。

  • 如果一個函數裡面有 step 了,那麼在 step 外面不允許有文本了。step 內不能再含其它 step。

@resumable

  • 當這條 step 語句產生的 SQL 執行失敗時,是否繼續執行下面的語句。無此參數預設為 false。也就是順序執行 step,當發生失敗的情況,整個 DAO 方法認為失敗。

map

<map id="messageModelMap" result="MessageModel">
  <result property="msgId" column="id"/>
</map>
  • 定義一個名為 messageModelMap 的映射,實際產生的 Objective-C 對象是 MessageModel 類。

  • Objective-C 對象的 msgId 屬性,對應表裡名為 id 的列值;未列的屬性認為和表的列名相同,可以省略。

upgrade

<upgrade toVersion="1.1">
  <step>
    alter table...
  </step>
  <step>
    alter table...
  </step>
</upgrade>
  • 隨著版本升級,資料庫可能有升級需求;處理升級的 SQL 陳述式寫在這裡。比如最初,設定檔 module 的版本是 1.0,升級後,設定檔的版本為 1.1。那麼新版本第一次運行 DAO 方法時,會檢測當前表的版本與設定檔的版本,當發現不一致時,會依次執行升級方法。這個方法會由底層自動調用,並在升級完成後再執行 DAO 方法。

  • upgrade 按照 SQL update 來執行,如果 SQL 不止一個,可以用 <step> 括起來,類似 <update id="createTable"> 的實現。

  • 如果需要使用 upgrade 塊定義的操作來升級表,那麼 module 裡的 resetOnUpgrade 必須設定為 false。

crypt

<crypt class="MessageModel" property="content"/>

描述 class 這個類的 property 屬性進行加密處理,當向資料庫寫入時從這個屬性裡取出的值會進行加密;當資料庫讀出時,產生對象向這個屬性裡賦值會先解密再賦值。

比如執行 DAO 方法,model 是 MessageModel 類。因為取了 model 的 content 屬性,所以會加密後再寫入資料庫。

<insert id="insertMessage" arguments="model">
     insert into ${T} (content) values(#{model.content})
</insert>

執行這個 select 方法時,返回對象是 MessageModel 類。底層從資料庫裡取出資料向 MessageModel 的執行個體寫入 content 屬性時,會將資料先解密再寫入。最後返回處理好的 MessageModel 對象。

<select id="getMessage" arguments="msgId" result="MessageModel">
     select * from ${T} where msgId = #{msgId}
</select>

加密方式的設定方法定義在 APDAOProtocol 中,如下:

/**
 *  設定加密器,用於加密表裡標記為需要加密的列的資料。向表裡寫資料時,碰到這個列,會調用進行加密。
 *
 *  @param crypt 加密結構體,會被拷貝一份。如果傳入的 crypt 是外部建立的,需要外部進行 free。如果是 APDefaultEncrypt(),不需要進行釋放。
 */
- (void)setEncryptMethod:(APDataCrypt*)crypt;

/**
 *  設定解密器,用於解密表裡標記為需要加密的列的資料。從表裡讀資料時,碰到這個列,會調用進行解密。
 *
 *  @param crypt 解密結構體,會被拷貝一份。如果傳入的 crypt 是外部建立的,需要外部進行 free。如果是 APDefaultDecrypt(),不需要進行釋放。
 */
- (void)setDecryptMethod:(APDataCrypt*)crypt;

如果不進行設定,會使用 APDataCenter 的預設加密,見 KV 儲存。如果一個 DAO 代理對象是 id<DAOProtocol>,並且 DAOProtocol@protocol<APDAOProtocol>,那麼可以直接用 DAO 代理對象調用 setEncryptMethodsetDecryptMethod 來設定加密、解密方法。

if

<insert id="addMessages" arguments="messages, onlyRead" foreach="messages.model">
    <if true="model.read or (not onlyRead)">
    insert or replace into ${T} (msgId, content, read) values(#{model.msgId}, #{model.content}, #{model.read})
    </if>
</insert>
  • 在 insert、update、delete、select 方法裡,可以嵌套使用 if 條件判斷語句。當 if 條件滿足時,會把 if 塊內的文本拼接到最終的 SQL 陳述式裡。

  • if 後可以接 true=”expr”,也可以接 false=”expr”; expr 為運算式,可以使用方法的參數,並且可以使用”.”來鏈式訪問參數對象的屬性。

  • 運算式支援的運算子如下:

    ():括弧

    +:正號

    -:負號

    +:加號

    -:減號

    *:乘號

    /:除號

    \:整除

    %:模數

    >:大於

    <:小於

    >=:大於等於

    <=:小於等於

    ==:等於

    !=:不等於

    and:邏輯與,不區分大小寫

    or:邏輯或,不區分大小寫

    not:邏輯非,不區分大小寫

    xor:異或,不區分大小寫

  • 大於符號、小於符號字元需要使用轉義。

  • 裡面的參數就是外部調用傳入的參數名,但是不要像 SQL 塊裡使用 #{} 或 @{} 包裹。

  • nil 含義同 Objective-C 裡的 nil。

  • 運算式裡的字串使用單引號起止,不支援逸出字元,但是支援 \’代表一個單引號。

  • 當參數為 Objective-C 對象時,支援用 . 來訪問它的屬性,比如上面例子 model.read,如果參數是數組或字典,可以用 參數名.count 取元素數。

一個較複雜的運算式如下:

<if true="(title != nil and year > 2010) or authors.count >= 2">

title,year,authors 都是調用者傳來的參數,調用時 title 是可以傳 nil 的;那麼上面含義為”當書名不為空白,並且出版年份大於 2010 年,或作者數大於 2。

choose

<choose>
  <when true="title != nil">
    AND title like #{title}
  </when>
  <when true="author != nil and author.name != nil">
    AND author_name like #{author.name}
  </when>
  <otherwise>
    AND featured = 1
  </otherwise>
</choose>
  • 實作類別似 switch 的文法,運算式要求類似 if 語句; when 後面也可以接 true=”expr”或 false=”expr”。

  • 只會執行第一個合格 when 或者 otherwise;可以沒有 otherwise。

foreach

<foreach item="iterator" collection="list" open="(" separator="," close=")" reverse="yes">
  @{iterator}
</foreach>
  • open、separator、close、reverse 可以省略。

  • item 表示迴圈變數,collection 表示迴圈數組參數名。

比如一個方法從外部接收字串數組參數,list 內容為 @[@"1", @"2", @"3"],有另一個參數是 prefix=@"abc",使用 () 包裹,使用 , 分隔。那麼執行結果為:(abc1,abc2,abc3)

<update id="proc" arguments="list, prefix">
   <foreach item="iterator" collection="list" open="(" separator="," close=")">
       {prefix}{iterator}
   </foreach>
</update>

foreach 語句通常用於拼接 select 語句裡的 in 塊,比如:

select * from ${T} where id in
<foreach item="id" collection="idList" open="(" separator="," close=")">
#{id}
</foreach>

where/set/trim

<where onVoid="quit"> 
  <if true="state != nil"> 
    state = #{state}
  </if> 
  <if true="title != nil">
    AND title like #{title}
  </if>
  <if true="author != nil and author.name != nil">
    AND author_name like #{author.name}
  </if>
</where>

where 會處理多餘的 AND、OR(大小寫無所謂),並在任何條件都不符合時連 where 都不返回。用於在 SQL 陳述式裡拼接有大量判斷條件的 where 子句。比如上例,如果只有最後一個判斷成立,該語句會正確返回 where author_name like XXX,而不是 where AND author_name like XXX。

<set>
  <if false="username != nil">username=#{username},</if>
  <if false="password != nil">password=#{password},</if>
  <if true="email != nil">email=#{email},</if>
  <if true="bio != nil">bio=#{bio},</if>
</set>

set 會處理結尾多餘的,,並在任何條件都不符合時什麼都不返回。與 where 語句類似,只是它處理的是尾碼的逗號。

<trim prefix="WHERE" prefixOverrides="AND | OR | and | or " onVoid="ignoreAfter">
</trim>
<!--
  等價於<where>
-->

<trim prefix="SET" suffixOverrides=",">
</trim>
<!--
  等價於<set>
-->
  • where 和 set 語句可以使用 trim 替換。Trim 語句定義了語句的整體首碼,以及對每個子句需要處理的多餘的首碼與尾碼列表(用 |劃分)。

  • onVoid 參數可以出現在 where、set、trim 裡,有兩個取值 ignoreAfter 和 quit。分別代表當這個 trim 語句裡任何子句都不成立,導致產生一個空串時,採取什麼邏輯。ignoreAfter 代碼忽略下面的格式化語句,直接返回當前產生的 SQL 陳述式執行,quit 代表不再執行這條 SQL 陳述式,但會返回成功。

sql

<sql id="userColumns"> id,username,password </sql>
<select id="selectUsers" result="{}">
  select ${userColumns}
  from some_table
  where id = #{id}
</select>

定義可重用的 SQL 程式碼片段,在其它語句中使用 ${name} 從原文引入進來。name 不能為 T 或 t,因為 ${T} 和 ${t} 代表預設的表名了。SQL 塊裡面可以再引用別的 SQL 塊。

try except

<insert id="insertTemplates" arguments="templates" foreach="templates.model">
    <try>
        insert into ${T} (tplId, tplVersion, time, data, tag) values(%{'#{model.*}', tplId, tplVersion, time, data, tag})
        <except>
            update ${T} set %{'* = #{model.*}', data, tag} where tplId = #{model.tplId} and tplVersion = #{model.tplVersion}
        </except>
    </try>
</insert>

有時,同一個 model 可能多次插入資料庫,用 insert or replace 會導致當 model 主鍵衝突(同主鍵的 model 已經在資料庫存在)時,原先的資料被刪除掉,重新 insert。這樣會導致同條資料的 rowid 發生變化。用 try except 語句塊可以解決這個問題(當然不僅限於解決這種問題)。try except 只能出現在 DAO Method 定義裡,前後不能再有其它語句。try 和 except 裡面可以包含其它語句塊。

當這條 DAO 方法執行時,如果 try 裡面的語句執行失敗,會自動嘗試執行 except 裡的語句。如果都失敗,這次 DAO 調用才會返回失敗。

引用方式

@ 引用

@{something},用於方法參數,參數名為 something,在格式化 SQL 陳述式時會把對象內容拼到 SQL 陳述式中;因為參數都為 id 類型,所以預設使用 [NSString stirngWithFormat:@”%@”, id] 來格式化;@{something or ""} 這種格式,表示傳入的參數如果為 nil,會轉成一個Null 字元串而不是 NULL。

不建議使用 @{} 的方式來引用參數,效率比較低,有 SQL 注入風險。如果參數對象是個 NSString,拼接進去後,會自動添加引號將字串括起來,保證 SQL 陳述式格式的正確性。如果使用者在設定檔裡自己寫了引號,底層不會自動添加引號了。

使用 @{something no ""},這種格式,可以強制不添加引號。

<select id="getMessage" arguments="id" result="[MessageModel]">
    select * from ${T} where id = @{id}
</select>

比如上例,id 參數傳進來是個 NSString,上面的寫法是正確的,產生的 SQL 會自動把 id 格式化進去,並且在前後添加引號。

# 引用

#{something},用於方法參數,參數名為 something,在格式化 SQL 陳述式時會轉為一個?,然後將對象綁定給 sqlite;建議書寫 SQL 時盡量使用這種方式,效率更高。 #{something or “”} 這種格式,表示傳入的參數如果為 nil,會轉成一個Null 字元串而不是 NULL。

$ 引用

${something},用來引用設定檔裡的內容,比如引用預設表名 ${T} 或 ${t}、引用設定檔裡定義的常量和 SQL 代碼塊。

鏈式訪問

對於 @ 和 # 引用,可以使用.來訪問參數對象的屬性。比如傳入的參數名是 model,並且是一個 MessageModel 類型,它有屬性 NSString* content。那麼 @{model.content},會取出其 content 屬性的值。內部實現為 [NSObject valueForKey:],所以如果參數是一個字典(字典的 valueForKey 等價於 dict[@""]),那麼也可以使用 #{adict.aaa} 引用 adict[@"aaa"] 值。

代理方法

每個產生的 DAO 對象代理對象都支援 APDAOProtocol。

@protocol MyDAOOperations <APDAOProtocol>
- (APDAOResult*)insertMessage:(MyMessageModel*)model;
@end

具體方法見代碼的函數注釋。

#import <Foundation/Foundation.h>
#import <sqlite3.h>
#import "APDataCrypt.h"
#import "APDAOResult.h"
#import "APDAOTransaction.h"

@protocol APDAOProtocol;

typedef NS_ENUM (NSUInteger, APDAOProxyEventType)
{
    APDAOProxyEventShouldUpgrade = 0,   // 即將升級
    APDAOProxyEventUpgradeFailed,       // 表升級失敗
    APDAOProxyEventTableCreated,        // 表被建立
    APDAOProxyEventTableDeleted,        // 表被刪除
};

typedef void(^ APDAOProxyEventHandler)(id<APDAOProtocol> proxy, APDAOProxyEventType eventType, NSDictionary* arguments);

/**
 *  這個 Protocol 定義的方法每個 DAO 代理對象都支援,使用時使用 id<APDAOProtocol>對 DAO 對象進行轉換。
 */
@protocol APDAOProtocol <NSObject>

/**
 *  設定檔裡 module 可以設定表名,如果想實現設定檔作為一個模板,操作不同的表,可以產生 DAO 對象後手工向 DAO 對象設計表名。
 *  比如要對與每個 id 的對話訊息進行分表的情況。
 */
@property (atomic, strong) NSString* tableName;

/**
 *  返回這個 proxy 操作的表所在資料庫檔案的路徑
 */
@property (atomic, strong, readonly) NSString* databasePath;

/**
 *  擷取這個 proxy 操作的資料庫檔案的控制代碼
 */
@property (atomic, assign, readonly) sqlite3* sqliteHandle;

/**
 *  註冊全域變數參數,這些參數設定檔裡的所有方法都可以使用,在設定檔裡使用#{name}和@{name}來訪問。
 */
@property (atomic, strong) NSDictionary* globalArguments;

/**
 *  這個 proxy 的事件回調,業務自己來設定。回調線程不確定。
    注意循環參考的問題,業務對象持有 proxy,這個 handler 方法裡不要訪問業務對象或 proxy,proxy 可以在回調的第一個參數裡拿到。
 */
@property (atomic, copy) APDAOProxyEventHandler proxyEventHandler;

/**
 *  設定加密器,用於加密表裡標記為需要加密的列的資料。向表裡寫資料時,碰到這個列,會調用進行加密。
 *
 *  @param crypt 加密結構體,會被拷貝一份。如果傳入的 crypt 是外部建立的,需要外部進行 free。如果是 APDefaultEncrypt(),不需要進行釋放。
 */
@property (atomic, assign) APDataCrypt* encryptMethod;

/**
 *  設定解密器,用於解密表裡標記為需要加密的列的資料。從表裡讀資料時,碰到這個列,會調用進行解密。
 *
 *  @param crypt 解密結構體,會被拷貝一份。如果傳入的 crypt 是外部建立的,需要外部進行 free。如果是 APDefaultDecrypt(),不需要進行釋放。
 */
@property (atomic, assign) APDataCrypt* decryptMethod;

/**
 *  返回 sqlite 的最後一條 rowId
 *
 *  @return sqlite3_last_insert_rowid()
 */
- (long long)lastInsertRowId;

/**
 *  擷取設定檔定義的所有方法列表
 */
- (NSArray*)allMethodsList;

/**
 *  刪除設定檔裡定義的表,可以用於特殊情況下的資料還原。刪除表後,DAO 對象仍可以正常使用,再次調用其它方法,會重新建立表。
 */
- (APDAOResult*)deleteTable;

/**
 *  刪除符合某個正則規則的所有表,請確保只刪除本 Proxy 操作的表,否則可能發生異常
 *
 *  @param pattern    Regex
 *  @param autovacuum 刪除完成是否自動調用 vacuum 清理資料庫空間
 *  @param progress   進度回調,可傳 nil,回調不保證主線程。為百分之後的結果
 *
 *  @return 操作是否成功
 */
- (APDAOResult*)deleteTablesWithRegex:(NSString*)pattern autovacuum:(BOOL)autovacuum progress:(void(^)(float progress))progress;

/**
 *  調用自己的資料庫連結執行 vacuum 操作
 */
- (void)vacuumDatabase;

/**
 *  DAO 對象可以自己把操作放在事務裡提升速度,實際調用的是該 DAO 對象操作的資料庫檔案 APSharedPreferences 的 daoTransaction 方法。
 */
- (APDAOResult*)daoTransaction:(APDAOTransaction)transaction;

/**
 *  建立一個資料庫副串連,為接下來可能發生的並發select 操作加速使用。可以調用多次,建立多個資料庫連接待用。
 *  這些建立的連結會自動關閉,業務層無須處理。

 *  @param autoclose 在空閑狀態多少秒後自動關閉,0 表示使用系統值
 */
- (void)prepareParallelConnection:(NSTimeInterval)autoclose;

@end