我们都知道, 不管使用任何一种高级编程语言, 都会遇到需要做本地缓存的情况. 当然, 本地缓存方式可能有多种方式. 但如果需要对本地缓存做灵活的操作, 基于sql 的数据库依然是首选. 本文将从基础的sql语句出发, 衍申到 iOS 开发中对数据库的应用.
一. SQL基础
SQL(结构化查询语言)给我们提供了数据库的创表,删表,数据的增删改查等基本操作. 接下来将依次举例演示这些操作.
创表
判断如果不存在 StudentTable 则创建. 将 id 设为主键, 以分号结尾.
create table if not exists StudentTable (id integer primary key, name text not null, score integer not null);
如果主键不需要外界输入, 而是通过默认的累加方式, 可以将主键设为自增模式.
create table if not exists StudentTable (id integer primary key autoincrement, name text not null, score integer not null);
主键有默认的唯一性, 如果需要将除主键意外的其他字段设置唯一性属性, 需要用unique 来修饰该字段.
create table if not exists StudentTable (id integer primary key autoincrement, name text unique not null, score integer not null);
删表
删除数据库表StudentTable
delete from StudentTable;
- 增
insert into StudentTable(id,name,score) values ('1','小明','99');
- 删
delete from StudentTable where id='1' and name='小明';
- 改
修改Student表中 id = 1 的这条数据, 同时修改两个字段. 修改name = '大明', 修改score='90' . 同时修改多个字段值时, 中间用逗号隔开.
update StudentTable set name='大明',score='90' where id='1';
- 查
查询 id = 1 的这条数据的所有字段值.
select * from StudentTable where id='1';
查询id = 1 的这条数据对应的name 的字段值.
select name from StudentTable where id = '1';
查询score = 90 的这条数据的name 的字段值, 并按照id 的升序排列. (如果需要降序排列, 将asc 替换成 desc 即可)
select name from StudentTable where score = '90' order by id asc;
二. FMDB中SQL的使用
FMDB 是在iOS开发中常用的第三方数据库操作框架, 其底层实现是对sqlite 的封装. 下文中用到的self.dataBase 是FMDatabase 的单例.
1. FMDatabase 调用 sql 不需要返回数据
FMDatabase 在调用sql 时, 不需要返回数据的情况有 增, 删, 改. 在这种情况下, FMDatabase 提供了两种调用方式.
- 通过
executeUpdate
直接调用组装好的sql, 不需要传入参数. (以增加一条记录为例)
NSString *sqlString = [NSString stringWithFormat:@"insert into StudentTable(id,name,score) values ('%@','%@','%@');", @1, @"小明", @99];
BOOL result = [self.dataBase executeUpdate:sqlString];
- 通过
executeUpdateWithFormat
调用sql. FMDataBase自带有格式化输入占位符 '?' ;
BOOL result = [self.dataBase executeUpdateWithFormat:@"insert into StudentTable(id,name,score) values (?,?,?)", @1, @"小明", @99];
2. FMDatabase 调用 sql 语句需要返回数据
FMDatabase 在调用sql 时,需要返回数据的操作是 查询. 查询操作也有以上两种调用方式
- 通过
executeQuery
直接调用组装好的sql, 不需要传入参数; - 通过
executeQueryWithFormat
调用sql. FMDataBase自带有格式化输入占位符 '?' ;
查询操作返回数据类型是FMResultSet, 需要对这个类型的数据进行解析, 转成模型数据.
- (NSArray *)selectStudentByID:(NSNumber *)ID {
NSMutableArray *resultArray = [NSMutableArray array];
FMResultSet *set = [self.dataBase executeQueryWithFormat:@"select * from StudentTable where id = ?;", ID];
while (set.next) {
Student *stu = [[Student alloc] init];
stu.ID = [set objectForColumnName:@"id"];
stu.name = [set objectForColumnName:@"name"];
stu.score = [set objectForColumnName:@"score"];
[resultArray addObject:key];
}
return resultArray;
}
三. 异步线程的FMDB
在项目中曾经遇到这么一种情况, 快速切换页面, 大量网络数据需要更新本地数据库的缓存. 操作的特点是: 数据量大, 对数据库的操作频繁, 更有甚者,多个网络数据流同时操作了同一张数据表. 那么这样的操作必然不能放到主线程中执行, 整个app 会卡顿的无法使用, 用户体验相当不好.
这时, 我考虑使用异步线程, 将这些耗时操作放到子线程中去执行. 事实上我也这么尝试了, app 卡顿的现象真的解决了. 在测试过程中, 新的问题又出现了. 问题就是: 多个动作同时操作数据库时, 未必所有动作都会被执行. 因为某张数据表正在被操作, 其他动作则无法操作而被跳过了. 这样, 每次从本地缓存中读取的数据就会和预期的有较大出入.
要解决以上问题, FMDB为我们提供了基于 NSOperationQueue 的多线程操作. 将所有操作放入队列中, iOS系统会自动分配子线程, 确保每个动作都会被执行, 不会因为数据库正在被操作而丢弃其他操作.
下文中用到的self.dataBaseQueue 是FMDatabaseQueue的单例.
- (void)insertStudentsArray:(NSArray *)studentsArray {
[self.dataBaseQueue inDatabase:^(FMDatabase *db) {
for (Student *stu in studentsArray) {
[self insertStu: stu];
}
}];
}
注意: FMDB提供的队列操作是不能嵌套的, 不能队列中再调用同一队列. 在上面的例子中, -(void)insertStu:(Student*)stu;
这个方法内部就不能再调用[self.dataBaseQueue inDatabase:^(FMDatabase *db) { }];
这个方法了, 否则程序会崩溃.