• 基础类
    • WCTDatabase
      • 创建
      • 打开
      • 关闭
      • 加密
      • 文件操作
      • 事务
    • WCTTable
    • WCTTransaction
    • 基础类共享
  • CRUD
    • 表操作
    • 数据操作
      • 便捷接口
        • 插入
        • 删除
        • 更新
        • 查找
      • 链式接口
  • 核心层接口
    • 调试SQL
  • 高级用法
    • 主键自增(Auto Increment)
    • as重定向
    • 多表查询

    本教程主要介绍WCDB-iOS/macOS中的基础类、CRUD(增删改查)与transaction(事务)的用法。

    阅读本教程前,建议先阅读iOS/macOS使用教程。

    基础类

    WCDB提供了三个基础类进行数据库操作:WCTDatabaseWCTTableWCTTransaction。它们的接口都是线程安全的。

    WCTDatabase

    WCTDatabase表示一个数据库,可以进行所有数据库操作,包括增删查改、表操作、事务、文件操作、损坏修复等。

    创建

    WCTDatabase通过initWithPath:接口进行创建。该接口会同时创建path中不存在的目录。

    1. NSString* path = @"~/Intermediate/Directories/Will/Be/Created/sample.db";
    2. WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
    3. database.tag = 1;

    打开

    WCDB大量使用延迟初始化(Lazy initialization)的方式管理对象,因此SQLite连接会在第一次被访问时被打开。开发者不需要手动打开数据库。

    通过canOpen接口可测试数据库是否能够打开。通过isOpened接口可测试数据库是否已经打开。

    1. if ([database canOpen]) {
    2. //...
    3. }
    4. if ([database isOpened]) {
    5. //...
    6. }

    关闭

    WCDB通过close接口直接关闭数据库

    1. [database close];

    由于WCDB支持多线程访问数据库,因此,该接口会阻塞等待所有数据库操作结束,以确保数据库完全关闭。

    对于一个特定路径的数据库,WCDB会在所有对象对其的引用结束时,自动关闭数据库,并且回收内存和SQLite连接。因此,大部分情况下开发者不需要手动关闭数据库。

    加密

    WCDB提供基于sqlcipher的数据库加密功能,如下:

    1. WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
    2. NSData *password = [@"MyPassword" dataUsingEncoding:NSASCIIStringEncoding];
    3. [database setCipherKey:password];

    文件操作

    WCDB提供了删除数据库、移动数据库、获取数据库占用空间和使用路径的文件操作接口。

    1. - (BOOL)removeFilesWithError:(WCTError **)error;
    2. - (BOOL)moveFilesToDirectory:(NSString *)directory withExtraFiles:(NSArray<NSString *> *)extraFiles andError:(WCTError **)error;
    3. - (NSArray<NSString *> *)getPaths;
    4. - (NSUInteger)getFilesSizeWithError:(WCTError **)error;

    文件操作不是一个原子操作。若一个线程正在操作数据库,而另一个线程进行移动数据库文件,可能导致数据库损坏。因此,文件操作的最佳实践是确保数据库已关闭。

    1. [database close:^{
    2. WCTError *error = nil;
    3. BOOL ret = [database moveFilesToDirectory:otherDirectory withError:&error];
    4. if (!ret) {
    5. NSLog(@"Move files Error %@", error);
    6. }
    7. }];

    关于文件操作的例子,可以参考Sample-file。

    事务

    WCTDatabase不支持跨线程事务。事务内的操作必须在同一个线程运行完。可以通过两种方式运行事务:

    • beginTransaction commitTransactionrollbackTransaction
    1. //[beginTransaction], [commitTransaction], [rollbackTransaction] and all interfaces inside this transaction should run in same thread
    2. BOOL ret = [database beginTransaction];
    3. WCTSampleTransaction *object = [[WCTSampleTransaction alloc] init];
    4. ret = [database insertObject:object
    5. into:tableName];
    6. if (ret) {
    7. ret = [database commitTransaction];
    8. } else {
    9. ret = [database rollbackTransaction];
    10. }
    • runTransaction:
    1. BOOL commited = [database runTransaction:^BOOL {
    2. WCTSampleTransaction *object = [[WCTSampleTransaction alloc] init];
    3. BOOL ret = [database insertObject:object
    4. into:tableName];
    5. //return YES to do a commit and return NO to do a rollback
    6. if (ret) {
    7. return YES;
    8. }
    9. return NO;
    10. }
    11. event:^(WCTTransactionEvent event) {
    12. NSLog(@"Event %d", event);
    13. }];

    跨线程事务可以参考WCTTransaction。

    关于事务的例子,可以参考Sample-transaction。

    WCTTable

    WCTTable表示一个表。它等价于预设了classtableNameWCTDatabase,仅可以进行数据的增删查改等。

    1. WCTTable* table = [database getTableOfName:tableName
    2. withClass:WCTSampleTable.class];

    WCTTransaction

    WCTTransaction表示一个事务。

    1. WCTTransaction *transaction = [database getTransaction];

    WCTDatabase的事务不同,WCTTransaction可以在函数和对象之间传递,实现跨线程的事务。

    1. //You can do a transaction in different threads using WCTTransaction.
    2. //But it's better to run serially, or an inner thread mutex will guarantee this.
    3. BOOL ret = [transaction begin];
    4. dispatch_async(dispatch_queue_create("other thread", DISPATCH_QUEUE_SERIAL), ^{
    5. WCTSampleTransaction *object = [[WCTSampleTransaction alloc] init];
    6. BOOL ret = [transaction insertObject:object
    7. into:tableName];
    8. if (ret) {
    9. [transaction commit];
    10. } else {
    11. [transaction rollback];
    12. }
    13. });

    关于事务的例子,可以参考Sample-transaction。

    基础类共享

    对于同一个路径的数据库,不同的WCTDatabaseWCTTableWCTTransaction对象共享同一个WCDB核心。因此,你可以在代码的不同位置、不同线程任意创建不同的基础类对象,WCDB会自动管理它们的共享数据和线程并发。

    1. WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:path];
    2. WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:path];
    3. database1.tag = 1;
    4. NSLog(@"%d", database2.tag);//print 1

    关于WCDB的架构和实现,可以参考:TODO

    CRUD

    WCDB的增删改查分为表操作和数据操作两种。

    表操作

    表操作包括创建/删除 表/索引、判断表、索引是否存在等。WCTDatabaseWCTransaction都支持表操作的接口。

    开发者可以根据ORM的定义创建表或索引:

    1. BOOL ret = [database createTableAndIndexesOfName:tableName
    2. withClass:WCTSampleTable.class];

    也可以通过WINQ自定义表或索引:

    1. BOOL ret = [database createTableOfName:tableName
    2. withColumnDefList:{
    3. WCTSampleTable.intValue.def(WCTColumnTypeInteger32),
    4. WCTSampleTable.stringValue.def(WCTColumnTypeString)
    5. }];

    关于表操作的例子,可以参考Sample-table

    数据操作

    数据操作分为便捷接口和链式接口两种。WCTDatabaseWCTTableWCTTransaction均支持数据操作接口。

    便捷接口

    便捷接口的设计原则为,通过一行代码即可完成数据的操作。

    插入
    • insertObject:into:insertObjects:into:,插入单个或多个对象
    • insertOrReplaceObject:intoinsertOrReplaceObjects:into,插入单个或多个对象。当对象的主键在数据库内已经存在时,更新数据;否则插入对象。
    • insertObject:onProperties:into:insertObjects:onProperties:into:,插入单个或多个对象的部分属性
    • insertOrReplaceObject:onProperties:intoinsertOrReplaceObjects:onProperties:into,插入单个或多个对象的部分属性。当对象的主键在数据库内已经存在时,更新数据;否则插入对象。
    删除
    • deleteAllObjectsFromTable:删除表内的所有数据
    • deleteObjectsFromTable:后可组合接 whereorderBylimitoffset以删除部分数据
    更新
    • updateAllRowsInTable:onProperties:withObject:,通过object更新数据库中所有指定列的数据
    • updateRowsInTable:onProperties:withObject:后可组合接 whereorderBylimitoffset以通过object更新指定列的部分数据
    • updateAllRowsInTable:onProperty:withObject:,通过object更新数据库某一列的数据
    • updateRowsInTable:onProperty:withObject:后可组合接 whereorderBylimitoffset以通过object更新某一列的部分数据
    • updateAllRowsInTable:onProperties:withRow:,通过数组更新数据库中的所有指定列的数据
    • updateRowsInTable:onProperties:withRow:后可组合接 whereorderBylimitoffset以通过数组更新指定列的部分数据
    • updateAllRowsInTable:onProperty:withRow:,通过数组更新数据库某一列的数据
    • updateRowsInTable:onProperty:withRow:后可组合接 whereorderBylimitoffset以通过数组更新某一列的部分数
    查找
    • getOneObjectOfClass:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据并组合成object
    • getOneObjectOnResults:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的部分列并组合成object
    • getOneRowOnResults:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的部分列并组合成数组
    • getOneColumnOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一列数据并组合成数组
    • getOneDistinctColumnOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一列数据,并取distinct后组合成数组。
    • getOneValueOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的某一列
    • getAllObjectsOfClass:fromTable:,取出所有数据,并组合成object
    • getObjectsOfClass:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一部分数据,并组合成object
    • getAllObjectsOnResults:fromTable:,取出所有数据的指定列,并组合成object
    • getObjectsOnResults:fromTable:后可接whereorderBylimitoffset以从数据库中取出一部分数据的指定列,并组合成object
    • getAllRowsOnResults:fromTable:,取出所有数据的指定列,并组合成数组
    • getRowsOnResults:fromTable:后可接whereorderBylimitoffset以从数据库中取出一部分数据的指定列,并组合成数组
      具体例子可直接参考Sample-convenience

    链式接口

    链式调用是指对象的接口返回一个对象,从而允许在单个语句中将调用链接在一起,而不需要变量来存储中间结果。

    WCDB对于增删改查操作,都提供了对应的类以实现链式调用

    • WCTInsert
    • WCTDelete
    • WCTUpdate
    • WCTSelect
    • WCTRowSelect
    • WCTMultiSelect
    1. WCTSelect *select = [database prepareSelectObjectsOnResults:Message.localID.max()
    2. fromTable:@"message"];
    3. NSArray<Message *> *objects = [[[[select where:Message.localID > 0]
    4. groupBy:{Message.content}]
    5. orderBy:Message.createTime.order()]
    6. limit:10].allObjects;

    whereorderBylimit等接口的返回值均为self,因此可以通过链式调用,更自然更灵活的写出对应的查询。

    开发者可以通过链式接口获取数据库操作的耗时、错误信息;也可以通过遍历逐个生成object。

    1. //Error message
    2. WCTError *error = select.error;
    3. //Performance
    4. int cost = select.cost;
    5. //Iteration
    6. Message *message;
    7. while ((message = [select nextObject])) {
    8. //...
    9. }

    关于链式接口的例子,请参考Sample-chaincall。

    核心层接口

    WCDB封装了常用的增删查改操作,但不可能覆盖所有SQL的用法。因此核心层提供了执行为封装的SQL的能力。

    1. - (BOOL)exec:(const WCDB::Statement &)statement;
    2. - (WCTStatement *)prepare:(const WCDB::Statement &)statement;

    结合WINQ,开发者可以用核心层接口执行其他未封装的复杂SQL。

    1. //run unwrapped SQL
    2. //PRAGMA case_sensitive_like=1
    3. [database exec:WCDB::StatementPragma().pragma(WCDB::Pragma::CaseSensitiveLike, true)];
    4.  
    5. //get value from unwrapped SQL
    6. //PRAGMA case_sensitive
    7. WCTStatement *statement = [database prepare:WCDB::StatementPragma().pragma(WCDB::Pragma::CacheSize)];
    8. if (statement && statement.step) {
    9. NSLog(@"Cache size %@", [statement getValueAtIndex:0]);
    10. }
    11.  
    12. //complex statement
    13. //EXPLAIN CREATE TABLE message(localID INTEGER PRIMARY KEY ASC, content TEXT);
    14. NSLog(@"Explain:");
    15. WCDB::ColumnDef localIDColumnDef(WCDB::Column("localID"), WCDB::ColumnType::Integer32);
    16. localIDColumnDef.makePrimary(WCDB::OrderTerm::ASC);
    17. WCDB::ColumnDef contentColumnDef(WCDB::Column("content"), WCDB::ColumnType::Text);
    18. WCDB::ColumnDefList columnDefList = {localIDColumnDef, contentColumnDef};
    19. WCDB::StatementCreateTable statementCreate = WCDB::StatementCreateTable().create("message", columnDefList);
    20. WCTStatement *statementExplain = [database prepare:WCDB::StatementExplain().explain(statementCreate)];
    21. if (statementExplain && [statementExplain step]) {
    22. for (int i = 0; i < [statementExplain getCount]; ++i) {
    23. NSString *columnName = [statementExplain getNameAtIndex:i];
    24. WCTValue *value = [statementExplain getValueAtIndex:i];
    25. NSLog(@"%@:%@", columnName, value);
    26. }
    27. }

    调试SQL

    [WCTStatistics SetGlobalSQLTrace:]会监控所有执行的SQL,该接口可用于调试,确定SQL是否执行正确。

    1. //SQL Execution Monitor
    2. [WCTStatistics SetGlobalSQLTrace:^(NSString *sql) {
    3. NSLog(@"SQL: %@", sql);
    4. }];

    关于核心层接口的例子,请参考Sample-core。

    高级用法

    主键自增(Auto Increment)

    对于主键自增的类,需要在ORM定义WCDB_PRIMARY_AUTO_INCREMENT(className, propertyName),然后通过isAutoIncrement接口设置自增属性,并通过lastInsertedRowID接口获取插入的RowID

    1. WCTSampleConvenient *object = [[WCTSampleConvenient alloc] init];
    2. object.isAutoIncrement = YES;
    3. object.stringValue = @"Insert auto increment";
    4. [database insertObject:object
    5. into:tableName];
    6. long long lastInsertedRowID = object.lastInsertedRowID;

    as重定向

    基于ORM的支持,我们可以从数据库直接取出一个Object。然而,有时候需要取出并非是某个字段,而是有一些组合。例如:

    1. NSNumber *maxModifiedTime = [database getOneValueOnResult:Message.modifiedTime.max()
    2. fromTable:@"message"];
    3. Message *message = [[Message alloc] init];
    4. message.createTime = [NSDate dateWithTimeIntervalSince1970:maxModifiedTime.doubleValue];

    这段代码从数据库中取出了消息的最新的修改时间,并以此将此时间作为消息的创建时间,新建了一个message。这种情况下,就可以使用as重定向。

    as重定向,它可以将一个查询结果重定向到某一个字段,如下:

    1. Message *message = [database getOneObjectOnResults:Message.modifiedTime.max().as(Message.createTime)
    2. fromTable:@"message"];

    通过as(Message.createTime)的语法,将查询结果重新指向了createTime。因此只需一行代码便可完成原来的任务。

    多表查询

    SQLite支持联表查询,在某些特定的场景下,可以起到优化性能、简化表结构的作用。

    WCDB同样提供了对应的接口,并在ORM的支持下,通过WCTMultiSelect的链式接口,可以同时从表中取出多个类的对象。

    1. /*
    2. SELECT contact.nickname, contact_ext.headImg
    3. FROM contact, contact_ext
    4. WHERE contact.name==contact_ext.name
    5. */
    6. WCTMultiSelect *multiSelect = [[database prepareSelectMultiObjectsOnResults:{
    7. Contact.nickname.inTable(@"contact"),
    8. ContactExt.nickname.inTable(@"contact_ext")
    9. } fromTables:@[ @"contact", @"contact_ext" ]] where:Contact.name.inTable(@"contact") == ContactExt.name.inTable(@"contact_ext")];
    10.  
    11. while ((multiObject = [multiSelect nextMultiObject])) {
    12. Contact *contact = (Contact *) [multiObject objectForKey:@"contact"];
    13. ContactExt *contact = (ContactExt *) [multiObject objectForKey:@"contact_ext"];
    14. //...
    15. }