今天看啥  ›  专栏  ›  99永远差一分

零基础iOS开发学习日记-功能篇-SQLite数据库

99永远差一分  · 掘金  ·  · 2021-06-01 16:22
阅读 66

零基础iOS开发学习日记-功能篇-SQLite数据库

开头

SQLite数据库

实际用处

  • 保存一些实效性要求不高的缓存数据,例如聊天记录、朋友圈内容
  • 保存用户信息

基础用法

  • 基础用法围绕增删改查进行
  • 数据库操作调用sqlite3.h文件,用的是C的函数,且由于OC的类型不像swift有Any,也没有自动数据类型的检查,所以会有点纠结
  • 在进行数据库造作之前,要初始化一个全局数据库句柄sqlite3 *db;,所有对数据库的操作都依赖于此句柄,类似于数据库的标记
  • 对应的可视化软件是Nacicat for SQLite

打开表

  • sqlite3_open(<#const char *filename#>, <#sqlite3 **ppDb#>)参数介绍
参数
1.数据库的全路径 Int8 -> uint8 -> byte / char c语言的字符串
2.全局数据库方法‘句柄’ -> 指针  
返回值 == SQLITE_OK 表示成功
如果数据库不存在,会创建数据库,如果数据库存在,会打开数据库
复制代码
//获取绝对路径 NSLibraryDirectory NSLibraryDirectory
NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
path = [path stringByAppendingPathComponent:@"demo.db"];
//const在指针左侧 修饰指针所指向的变量不变
//const在指针右侧 修饰指针 地址不变
//NSString -> char
const char *cPath = [path cStringUsingEncoding:NSUTF8StringEncoding];
//打开,要进行错误判断
if (sqlite3_open(cPath, &db) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

创建表

  • 创表SQL语句格式
CREATE TABLE 创建表
IF NOT EXISTS 判断表是否存在,如果存在就不执行
'T_Person' 表名
(
    字段 INTEGER(整数)/ real(小数)/ TEXT (字符串)/ BOLB(二进制数据-通常不会保存在数据库中)
    NOT NULL 不允许为空,主键必须有内容
    PRIMARY KEY 主键
    AUTOINCREMENT 自动增长
    , 逗号区分字段,最后一个字段不需要
)
复制代码
  • SQL执行语句sqlite3_exec(<#sqlite3 *#>, <#const char *sql#>, <#int (*callback)(void *, int, char **, char **)#>, <#void *#>, <#char **errmsg#>)
1.全局数据库句柄
2.要执行的sql
3.执行完成sql的调用的c语言函数指针,通常传入nil
4.第三个参数的函数参数的地址,通常传入nil
5.错误信息,有其他方式获取执行情况,通常传入nil
复制代码
//创建
NSString *sql = @"CREATE TABLE IF NOT EXISTS 'local' ("
"'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
"'name' TEXT,"
"'age' INTEGER,"
"'adult' blob,"
"'height' real"
");";
const char *cSql = [sql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
if (sqlite3_exec(db, cSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

插入

  • 插入SQL语句格式
INSERT INTO  往哪个数据表里插入数据
(字段1, 字段2...)
VALUES 值
(值1, 值2....)
复制代码
NSString *insertSql = @"INSERT INTO local (id, name, age, adult, height) VALUES (2, 'liliu', 18, 0, 175.5);";
const char *cInsertSql = [insertSql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
if (sqlite3_exec(db, cInsertSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

删除

  • 删除SQL语句格式
DELETE FROM 从哪个表里删
WHERE 主键 = id
复制代码
NSString *deleteSql = @"DELETE FROM local WHERE id = 2;";
const char *cDeleteSql = [deleteSql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
if (sqlite3_exec(db, cDeleteSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

修改

  • 修改SQL语句格式
UPDATE 表名
SET 
字段1 = 值1,字段2 = 值2 ...
WHERE 主键 = id
复制代码
NSString *updateSql = @"UPDATE local SET name = 'wangwu', age = 80 WHERE id = 2;";
const char *cUpdateSql = [updateSql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
if (sqlite3_exec(db, cUpdateSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

代替

  • 注意
1.sql语句中必须包含主键
2.如果指定的主键不存在,新增记录
3.如果指定的主键存在,修改记录
条件 主键不能是自增长的,因为主键数值需要指定
复制代码
  • SQL语句
INSERT OR REPLACE INTO Test (id, name, age) VALUES (2, 'wei', 99);
复制代码
NSString *replaceSql = @"INSERT OR REPLACE INTO Test (id, name, age) VALUES (2, 'wei', 99);";
const char *cReplaceSql = [replaceSql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
if (sqlite3_exec(db, cReplaceSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"success");
复制代码

查询

  • 查询SQL语句格式
-- 查询所有记录,不建议写 * 通配符,直接阅读代码,无法知道查询返回的内容,可以用在测试中
-- SELECT * FROM T_Person;

-- 写明字段,便于阅读
-- SELECT id, name, height, age FROM T_Person;

-- 查询数量
SELECT count(*) FROM T_Person;

-- 指定条件统计查询
SELECT count(*) FROM T_Person WHERE height > 1.5;
-- 查询最大值 应用场景:查询经验最高的值,最近加入的人...
SELECT MAX(age) FROM T_Person;

-- 分页 LIMIT 从第几条开始(起始的条数是0),返回的记录行数
-- SELECT id, name, height, age FROM T_Person
-- LIMIT 1, 2;

-- LIMIT WHERE 组合使用,方便的作出分页功能
SELECT id, name, height, age FROM T_Person
WHERE id >= 3
LIMIT 2;

-- 排序 默认升序 ASC / 降序 DESC
-- 排序是按照指定的条件,由左至右依次排列,先排列左边的条件
SELECT id, name, height, age FROM T_Person
ORDER BY name DESC, age ASC;

-- 模糊查询
-- %可以匹配任何内容,%内容% 表示只要出现了这个内容,都会被搜索出来
-- 逻辑判断 AND OR NOT
SELECT id, name, height, age FROM T_Person
WHERE (name LIKE '%a%' AND age > 80) OR name = 'wangwu';
复制代码
  • 预编译SQL函数sqlite3_prepare_v2(<#sqlite3 *db#>, <#const char *zSql#>, <#int nByte#>, <#sqlite3_stmt **ppStmt#>, <#const char **pzTail#>)
1.全局数据库句柄
2.要执行sql的c语言字符串
3.要执行sql的以字节为单位的长度,但是如果传入-1,sql框架会自动计算
4.stmt 预编译指令,后续针对本次查询,所有的操作都基于本句柄,一定要释放;通过step函数,能够顺序获取其中的结果
5.关于stmt尾部参数的指针,通常传入nil
返回值 如果编译成功,表示能够正常执行 返回 SQLITE_OK
复制代码
NSString *querySql = @"SELECT * FROM local;";
const char *cQuerySql = [querySql cStringUsingEncoding:NSUTF8StringEncoding];
//执行语句
//预编译指令,可以想成每一行
sqlite3_stmt *stmt;
//预编译
if (sqlite3_prepare_v2(db, cQuerySql, -1, &stmt, nil) != SQLITE_OK) {
    NSLog(@"failed");
    sqlite3_finalize(stmt);
    return;
}
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:10];
//有下一行的时候就执行
while (sqlite3_step(stmt) == SQLITE_ROW) {
    //获得字段数
    int cols = sqlite3_column_count(stmt);
    NSMutableDictionary *rowDic = [NSMutableDictionary new];
    //循环每一个字段
    for (int col = 0; col < cols; col++) {
        //获得字段名
        const char  *cName = (const char *)sqlite3_column_name(stmt, col);
        //char -> NSString
        NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
        //获得值
        const char *cValue = (const char *)sqlite3_column_text(stmt, col);
        NSString *value = [NSString stringWithCString:cValue encoding:NSUTF8StringEncoding];
        //赋值
        rowDic[name] = value;
    }
    //保存一条完整的信息
    [arr addObject:rowDic];
}
NSLog(@"success");
NSLog(@"%@", arr);
复制代码

小结

  • 不难看出,除了查询的操作比较特殊外,增删改只需要准备好对应的语句,并且用sqlite3_exec调用即可
  • 而SQL语句的获得,可以通过可视化软件Nacicat for SQLite来获得

事务

实际用处

  • 处理大量数据需要打开,提高效率
  • 在SQL中,如果不显式地开启事务,每一条数据库的操作指令都会开启事务,执行完毕后,提交事务;如果显式地调用,就不再开启事务
  • 打开后,会对数据库进行快照,修改数据库成功则继续,失败则恢复快照,快照实质上就是先前的状态
  • 事务中,如果错误要进行回滚,错误中要进行break

基础用法

  • 下面以插入20000条数据为例,可以试试不用事物的插入时间
//获取当前时间
CFTimeInterval start = CACurrentMediaTime();
//开启事务
NSString *transSql = @"BEGIN TRANSACTION;";
const char* cTransSql = [transSql cStringUsingEncoding:NSUTF8StringEncoding];
if (sqlite3_exec(db, cTransSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"TRANSACTION success");
//插入大量数据
for (int i = 0; i < 20000; i++) {
    NSString *insertSql = [NSString stringWithFormat:@"INSERT INTO local (name, age, adult, height) VALUES ( 'liliu-%d', 18, 0, 175.5);", i];
    const char *cInsertSql = [insertSql cStringUsingEncoding:NSUTF8StringEncoding];
    //执行语句
    if (sqlite3_exec(db, cInsertSql, nil, nil, nil) != SQLITE_OK) {
        NSLog(@"failed");
        return;
    }
    //模拟错误
//        if (i == 1000) {
              //错误回滚
//            NSString *rollbackSql = @"ROLLBACK TRANSACTION;";
//            const char* cRollbackSql = [rollbackSql cStringUsingEncoding:NSUTF8StringEncoding];
//            if (sqlite3_exec(db, cRollbackSql, nil, nil, nil) != SQLITE_OK) {
//                NSLog(@"failed");
//                return;
//            }
//            NSLog(@"ROLLBACK success");
              //中断
//            break;
//        }
}
//如果插入正确,则提交事务
NSString *commitSql = @"COMMIT TRANSACTION;";
const char* cCommitSql = [commitSql cStringUsingEncoding:NSUTF8StringEncoding];
if (sqlite3_exec(db, cCommitSql, nil, nil, nil) != SQLITE_OK) {
    NSLog(@"failed");
    return;
}
NSLog(@"COMMIT success");
//获取整个流程的时间 0.231517
NSLog(@"%f", CACurrentMediaTime() - start);
复制代码

性能测试

  • 取绝对时间的函数
CFAbsoluteTimeGetCurrent()  核心框架的 会收到系统服务的影响,进行时间校准;在做性能测试的时候,会有误差
CACurrentMediaTime() 核心动画的,只和硬件时间有关
复制代码

总结

  • iOS操作数据库,最重要的点就是SQL语句的编写,最好是要经过可视化软件测试后,在写入程序
  • 在实际开发中,一行数据其实就是对应一个对象,所以往往是将数据库中的字段封装成对象的属性,方便操作
  • 而数据库执行方法有很多一样的地方,往往是封装成一个单例管理类,进行操作,结构一般为控制器调用对象对象调用数据库管理类

FMDM

  • 放在第三方库去整理



原文地址:访问原文地址
快照地址: 访问文章快照