swift优秀框架

iOS 数据库操作

2023-05-18  本文已影响0人  大成小栈

1. SQLite

稍后要介绍的FMDB和WCDB都是基于SQLite的封装。当然,iOS也可以直接使用SQLite,系统中内置了libsqlite的库。

1.1 配置工作

首先,要添加库文件libsqlite3.tbd,

其次,导入头文件 sqlite3.h,

#import <sqlite3.h>
1.2 使用sqlite的基本过程
//生成路径
+(NSString *)path{

    NSArray *documentArr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentPath = [documentArr firstObject];
    // crylown.db 为数据库的名字
    NSString *path = [NSString stringWithFormat:@"%@/student.sqlite",documentPath];
    NSLog(@"位置path == %@",path);
    return path;
}

//创建/打开数据库
-(void)createDataBase{

    int databaseResult = sqlite3_open([[SqliteObject path] UTF8String], &database);
    if (databaseResult == SQLITE_OK) {
          [self createForm];
    }
    else {
          NSLog(@"创建/打开数据库失败,%d",databaseResult);
    }
}

//创建表
-(void)createForm{

         char *error = NULL;

    //    建表格式: create table if not exists 表名 (列名 类型,....)    注: 如需生成默认增加的id: id integer primary key autoincrement
        const char *createSQL = "create table if not exists t_students (id integer primary key autoincrement, name text, age integer, sex text)";
        // 执行sql语句
        /**
           第1个参数:数据库对象
           第2个参数:sql语句
           第3个参数:查询时候用到的一个结果集闭包
           第4个参数:用不到
           第5个参数:错误信息
         */
        int tableResult = sqlite3_exec(database, createSQL, NULL, NULL, &error);

        if (tableResult == SQLITE_OK) {
            NSLog(@"创建成功");
        }
        else {
            NSLog(@"创建表失败:%s",error);
        }
}

// 插入数据
- (void)insertData:(NSString*)name ageinteger:(int)age sexStr:(NSString*)sex {
        // 拼接 sql 语句
        NSString *sql = [NSString stringWithFormat:@"insert into t_students (name,age,sex) values ('%@',%d,'%@');",name,age,sex];
        // 执行 sql 语句
        char *errMsg = NULL;
        int result = sqlite3_exec(database, sql.UTF8String, NULL, NULL, &errMsg);

        if (result == SQLITE_OK) {
            NSLog(@"插入数据成功 - %@",name);
        } 
        else {
            NSLog(@"插入数据失败 - %s",errMsg);
        }
}

// 查询操作
- (void)sqlData {
    // sql语句
    const char *sql="SELECT id,name,age,sex FROM t_students WHERE age<20;";
    sqlite3_stmt *stmt = NULL;
    /**
         第1个参数:一个已经打开的数据库对象
         第2个参数:sql语句
         第3个参数:参数2中取出多少字节的长度,-1 自动计算,\0停止取出
         第4个参数:准备语句
         第5个参数:通过参数3,取出参数2的长度字节之后,剩下的字符串
       */
    // 进行查询前的准备工作
    if (sqlite3_prepare_v2(database, sql, -1, &stmt, NULL) == SQLITE_OK) {   // sql语句没有问题
        NSLog(@"sql语句没有问题");

        // 每调用一次sqlite3_step函数,stmt就会指向下一条记录
        while (sqlite3_step(stmt) == SQLITE_ROW) {  // 找到一条记录
            // 取出数据
            int ID = sqlite3_column_int(stmt, 0);   // 取出第0列字段的值
            const unsigned char *name = sqlite3_column_text(stmt, 1);   // 取出第1列字段的值
            int age = sqlite3_column_int(stmt, 2);  // 取出第2列字段的值
            const unsigned char *sex = sqlite3_column_text(stmt, 3);
            printf("%d %s %d\n %s",ID,name,age,sex);
        }
    } 
    else {
        NSLog(@"查询语句有问题");
    }
}

//修改数据
-(void)updateData {

    //其实Sqlite的数据插入,修改,删除执行的方法都是一样的只是执行的sql语句不一样,想知道sql的更多语句操作自行百度了,比较多这里就不讲解了,只介绍一些基本的操作方法。
    //sqlite3数据(把年龄大于60的学生名字全部改成‘哈哈’)
    NSString *sql = @"update t_students set name = '哈哈' where age > 60";
    char *errorMesg = NULL;
    int result = sqlite3_exec(database, sql.UTF8String, NULL, NULL, &errorMesg);
    if (result == SQLITE_OK) {

            NSLog(@"更改成功");
        }
        else {

            NSLog(@"更改失败");
        }
    //然后执行查询语句就能看到更改后的效果了
}

//删除数据
-(void)deleteData {
    //删除表中年龄大于60的学生数据
    NSString *sql = @"delete from t_students where age >= 60";
    char *errorMesg = NULL;
    int result = sqlite3_exec(database, sql.UTF8String, NULL, NULL, &errorMesg);
    if (result == SQLITE_OK) {
        NSLog(@"删除成功");
    }
    else {
        NSLog(@"删除失败");
    }
}

1.3 SQLite基础

注意:SQL语句不区分大小写(CREATE = create);每条语句以分号(;)结尾;关键字建议大写。

  1. 一般的使用方法是创建一个单例管理类,专门管理数据库的操作。
  2. 开发数据库的步骤:
    a.建立数据库 → 新加了本地数据库文件。
    b.创建数据表 → 每张数据表存储一类数据
    c.利用SQL命令实现增删查改,并在UI显示

SQL命令:
a) DDL–数据定义语言:CREATE(创建新表)、ALTER(修改更新表)、DROP(删除整个表)
b) DML-数据操作语言:INSERT(插入)、UPDATE(更新)、DELETE(删除)
c) DQL-数据查询语言:SELECT(查询)

SQLite C语言库操作
a) 在工程中导入SQlite框架:libsqlite3.dylib
b) sqlite3_open打开数据库,如果数据库不存在会新建,返回数据库的句柄
c) sqlite3_exec参数为数据库句柄,执行SQL命令,实现创建Table,增删查改操作。
实质就是sqlite3_prepare(把字符串转化为SQL命令对象)和sqlite3_step(执行SQL命令对象)和sqlite3_finalize(销毁SQL对象,否则内存泄漏)的结合体。sqlite3_column取指定列数据,用于查询数据,包括获取sqlite3_column_name列名、sqlite3_column_type数据类型等
d) sqlite3_close关闭数据库,参数为句柄,不过一般打开后就不会关闭数据库,提高效率。

https://www.cnblogs.com/zhun/p/5543268.html
https://www.jianshu.com/p/4a0e6773c694
https://www.jianshu.com/p/2333fad79f2f
https://blog.csdn.net/swanzhu/article/details/48475039

由于Apple提供的CoreData框架差强人意,使得开发者们纷纷将目光投向开源社区,寻找更好的存储方案。

2. FMDB

FMDB将C语言的iOS系统的SQLite数据库的操作代码用OC进行封装,面向对象,容易理解和使用;对比苹果自带的Core Data框架,更加轻量级和灵活,并且提供了线程不安全的解决方案。

https://github.com/ccgus/fmdb

2.1 使用说明

cocopads 引入 FMDB 库

pod 'FMDB'

2.2 打开数据库

通过指定SQLite数据库文件路径来创建FMDatabase对象

// 1..创建数据库对象
FMDatabase *db = [FMDatabase databaseWithPath:path];
// 2.打开数据库
if ([db open]) {
    // do something
} else {
    DLog(@"fail to open database");
}

/*
文件路径有三种情况:
1)具体文件路径:如果不存在会自动创建,建议使用绝对路径;
2)空字符串@"":会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除;
3)nil:会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁。
【备注】path 可以是相对路径,也可以是绝对路径。
/*

建表操作

NSString *createTableSqlString = @"CREATE TABLE IF NOT EXISTS t_student (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL)";
[db executeUpdate:createTableSqlString];

2.3 数据库的增删改查

使用executeUpdate: 方法执行更新,

//// 1.示例INSERT(写入数据)
//不确定的参数用?来占位
NSString *sql = @"insert into t_student (name, age) values (?, ?)";
[db executeUpdate:sql, @"zhangsan", [NSNumber numberWithInt:18]];

//// 2.示例DELETE(删除数据)
NSString *sql = @"delete from t_student where id = ?";
[db executeUpdate:sql, [NSNumber numberWithInt:1]];

//// 3.示例UPDATE(更改数据)
NSString *sql = @"update t_student set name = "heiheihei"  where id = ?";
[db executeUpdate:sql, [NSNumber numberWithInt:1]];

// -(BOOL)executeUpdateWithFormat:(NSString*)format, ...

//不确定的参数用%@,%d等来占位
NSString *sql = @"insert into t_student (name,age) values (%@,%i)";
[db executeUpdateWithFormat:sql, @"zhangsan", 18];

// -(BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)

NSDictionary *studentDict = [NSDictionary dictionaryWithObjectsAndKeys:@"lisi", @"name", @"18", @"age", nil];
[db executeUpdate:@"insert into t_student (name, age) values (:name, :age)" withParameterDictionary:studentDict];  

//// 4.执行查询操作
//查询方法
// 1)(FMResultSet *)executeQuery:(NSString*)sql, ...
// 2)(FMResultSet *)executeQueryWithFormat:(NSString*)format, ...

// 查询
NSString *sql = @"select id, name, age FROM t_student";
FMResultSet *rs = [db executeQuery:sql];
while ([rs next]) {
    int id = [rs intForColumnIndex:0];
    NSString *name = [rs stringForColumnIndex:1];
    int age = [rs intForColumnIndex:2];
    NSDictionary *studentDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:id], @"id", name, @"name", [NSNumber numberWithInt:age], @"age", nil];
    [studentArray addObject:studentDict];
}
2.4 多语句和批处理

FMDatabase 可以通过 -executeStatements:withResultBlock: 方法在一个字符串中执行多语句。

NSString *sql = @"CREATE TABLE IF NOT EXISTS bulktest1 (id integer PRIMARY KEY AUTOINCREMENT, x text);"
                "CREATE TABLE IF NOT EXISTS bulktest2 (id integer PRIMARY KEY AUTOINCREMENT, y text);"
                "CREATE table IF NOT EXISTS bulktest3 (id integer primary key autoincrement, z text);"
                "insert into bulktest1 (x) values ('XXX');"
                "insert into bulktest2 (y) values ('YYY');"
                "insert into bulktest3 (z) values ('ZZZ');"
        ;
        result = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
        "select count(*) as count from bulktest2;"
        "select count(*) as count from bulktest3;";

        result = [db executeStatements:sql withResultBlock:^int(NSDictionary *resultsDictionary) {
            NSLog(@"dictionary=%@", resultsDictionary);
            return 0;
        }];
2.5 队列和线程安全

在多线程中同时使用 FMDatabase 单例是极其错误的想法,会导致每个线程创建一个 FMDatabase 对象。不要跨线程使用单例,也不要同时跨多线程,不然会奔溃或者异常。因此不要实例化一个 FMDatabase 单例来跨线程使用,相反使用FMDatabaseQueue,下面就是它的使用方法:

//// 创建队列
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

// 示例:
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];

    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    while ([rs next]) {
        ...
    }
}];

把操作放在事务中也很简单,示例:

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];

    if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
    }
    // ...
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
}];

很好的示例:
https://www.cnblogs.com/hero11223/p/6057186.html

fmdb使用中问题调试
http://blog.devtang.com/2012/04/22/use-fmdb/
http://www.hcios.com/archives/921

3. WCDB

转自:https://cloud.tencent.com/developer/article/2256743

目前移动端数据库方案按其实现可分为两类,

可见,各个方案都有其独特的优势及劣势,没有最好的,只有最适合的。

好的数据库应满足:

显然,上述各个方案都不能完全满足微信的需求。

于是,我们造了这个“轮子” - WCDB-iOS/Mac

WCDB-iOS/Mac


WCDB-iOS/Mac(以下简称WCDB,均指代WCDB的iOS/Mac版本),是一个基于SQLite封装的Objective-C++数据库组件,提供了如下功能:

WCDB覆盖了数据库使用的绝大部分场景,且经过微信海量用户的验证,并将持续不断地增加新的能力。

本文是WCDB系列文章的第一篇,主要介绍WCDB-iOS/Mac的基本用法,包含:

ORM


在WCDB内,ORM(Object Relational Mapping)是指

这一过程。通过ORM,可以达到直接通过Object进行数据库操作,省去拼装过程的目的。

WCDB通过内建的宏实现ORM的功能。如下

对于一个已有的ObjC类,

简单几行代码,就完成了将类和需要的字段绑定到数据库表的过程。这三个宏在名称和使用习惯上,也都和定义一个ObjC类相似,以此便于记忆。

除此之外,WCDB还提供了许多可选的宏,用于定义数据库索引、约束等,如:

定义完成后,只需要调用createTableAndIndexesOfName:withClass:接口,即可创建表和索引。

接口会根据ORM的定义,创建对应表和索引。

CRUD


得益于ORM的定义,WCDB可以直接进行通过object进行增删改查(CRUD)操作。

Transaction


WCDB内可通过两种方式执行Transaction(事务),一是runTransaction:接口

这种方式要求数据库操作在一个BLOCK内完成,简单易用。

另一种方式则是获取WCTTransaction对象

WCTTransaction对象可以在类或函数间传递,因此这种方式也更具灵活性。

WINQ


有心的同学可能会注意到上述例子中的一些特殊语法:

这个便是WINQ。

WINQ(WCDB Integrated Query,音'wink'),即WCDB集成查询,是将自然查询的SQL集成到WCDB框架中的技术,基于C++实现。

传统的SQL语句,通常是开发者拼接字符串完成。这种方式不仅繁琐、易错,而且出错后很难定位到问题所在。同时也容易给SQL注入留下可乘之机。

而WINQ将查询语言集成到了C++中,可以通过类似函数调用的方式来写SQL查询。借用IDE的代码提示和编译器的语法检查,达到易用、纠错的效果。

对于一个已绑定ORM的类,可以通过className.propertyName的方式,获得数据库内字段的映射,以此书写SQL的条件、排序、过滤等等所有语句。如下是几个例子:

由于WINQ通过接口调用实现SQL查询,因此在书写过程中会有IDE的代码提示和编译器的语法检查,从而提升开发效率,避免写错。

WINQ的接口包括但不限于:

凡是SQLite支持的语法规则,WINQ基本都有其对应的接口。且接口名称与SQLite的语法规则基本保持一致。对于熟悉SQL的开发者,无须特别学习即可立刻上手使用。

高级用法


as重定向

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

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

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

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

链式调用

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

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

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

传统的接口方便快捷,可以直接获得操作结果;链式接口则更具灵活性,开发者可以获取数据库操作的耗时、错误信息;也可以通过遍历逐个生成object。

WCDB内同时支持这两种接口,优势互补,开发者可以根据需求,选择使用。

多表查询

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

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

类字段绑定

在ORM中,我们通过宏,将ObjC类的property绑定为数据库的一个字段。但并非所有property的类型都能绑定到字段。

WCDB内置支持的类型有:

然而,内置支持得再多,也不可能完全覆盖开发者所有的需求。因此WCDB支持开发者自定义类字段绑定。

类只需实现WCTColumnCoding协议,即可支持绑定。

为了简化定义,WCDB提供了文件模版来创建类字段绑定。

  1. 首先需要安装文件模版。该模版的安装脚本集成在WCDB的编译脚本中,只需编译一次WCDB,就会自动安装文件模版。安装完成后重启Xcode,新建文件,即可看到对应的文件模版
  1. 选择WCTColumnCoding
  1. 我们知道NSDate是遵循NSCoding协议的,因此这里选择了Binary类型。即,将NSDate以二进制数据的形式存到数据库中。完成后会自动创建如下的文件模版:
  1. 然后只需将NSDate和NSData互相转换的方式填上去即可。如下:
image.png

总结


WCDB通过ORM和WINQ,体现了其易用性上的优势,使得数据库操作不再繁杂。同时,通过链式调用,开发者也能够方便地获取数据库操作的耗时等性能信息。而高级用法则扩展了WCDB的功能和用法。

上一篇 下一篇

猜你喜欢

热点阅读