概念
FMDB 是 iOS 平台的 SQLite 数据库框架; FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API。以面相对象的方式操作数据库。
FMDB的优点
1) 使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码;
2) 对比苹果自带的 Core Data 框架,更加轻量级和灵活;
3) 提供了多线程安全的数据库操作方法,有效地防止数据混乱;
API讲解:
FMDB的三个主要类别:
FMDatabase ——代表一个 SQLite 数据库。 用于执行 SQL 语句。
FMResultSet ——执行查询一个 FMDatabase 的结果集。
FMDatabaseQueue ——在多线程操作更新和查询时,需要使用这个类,它是线程安全的。
非线程安全数据库创建方法:
/**
* @brief 创建一个数据库对象
数据库被创建时,数据库文件路径有三种情况:
1)具体文件路径 : 如果不存在会自动创建;
2)空字符串@"" : 会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除;
3)nil : 会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁;
*
* @param inPath 数据库路径(绝对路径)
*
* @return 返回对象结果
*/
+ (instancetype)databaseWithPath:(NSString*)inPath;
/**
* @brief 打开数据库
*
* @return YES,成功;NO,失败
*/
- (BOOL)open;
/**
* @brief FMDB 中该方法相当于 `CREATE`,`UPDATE`,`INSERT`,`DELETE`(增删改)
*
* @param sql 指定执行的 SQL 语句
*
* @return YES,执行成功;NO,失败
*/
- (BOOL)executeUpdate:(NSString*)sql, ...;
线程安全数据库创建:
/**
* @brief 创建数据库实例(查看源码可知:调用该方法后,数据库已打开)
*
* @param aPath 数据库绝对路径
*
* @return 返回数据对线程对象
*/
+ (instancetype)databaseQueueWithPath:(NSString*)aPath;
/**
* @brief 同步队列执行数据库操作(dispatch_sync实现,该方法线程安全)
*
* @param block 回调数据库本身,我们可以在代码块内操作数据库
*/
- (void)inDatabase:(void (^)(FMDatabase *db))block;
代码
非线程安全代码:
///=============================================================================
/// @name 非线程安全数据库
///=============================================================================
#pragma mark - 创建非线程安全的数据库
- (void)createNonatomicDBWithDbPath:(NSString *)dbPath {
// 创建数据库的实例
self.db = [FMDatabase databaseWithPath:dbPath];
// 打开数据库
BOOL flag = [self.db open];
if (flag) {
DLog(@"db open success.");
} else {
DLog(@"db open failed.");
}
// 创建表
BOOL exeFlag = [self.db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_kingdev (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT NOT NULL, height REAL NOT NULL)"];
if (exeFlag) {
DLog(@"CREATE success");
} else {
DLog(@"CREATE failed");
}
}
// 增
- (IBAction)insertInto:(id)sender {
BOOL flag = [self.db executeUpdate:@"INSERT INTO t_kingdev (name, phone, height) VALUES (?, ?, ?)", [NSString stringWithFormat:@"张%d", arc4random_uniform(50)], [NSString stringWithFormat:@"%d", arc4random_uniform(20)], [NSNumber numberWithFloat:(arc4random_uniform(1000) / 10.f)]];
if (flag) {
DLog(@"INSERT INTO success");
} else {
DLog(@"INSERT INTO failed");
}
}
// 删
- (IBAction)delete:(id)sender {
// 删除 id 大于 20 的数据库数据
BOOL flag = [self.db executeUpdate:@"DELETE FROM t_kingdev WHERE id > 20"];
if (flag) {
DLog(@"DELETE success");
} else {
DLog(@"DELETE failed");
}
}
// 改
- (IBAction)update:(id)sender {
// '张三' ——> 改为 'Flora'
[self.db executeQuery:@"update t_kingdev set name = 'Flora' where name = '张三';"];
}
// 查
- (IBAction)select:(id)sender {
FMResultSet *resultSet = [self.db executeQuery:@"select * from t_kingdev"];
// 从结果集往下找
while ([resultSet next]) {
// 根据字段取值
NSString *name = [resultSet stringForColumn:@"name"];
NSString *phone = [resultSet stringForColumn:@"phone"];
double height = [resultSet doubleForColumn:@"height"];
DLog(@"name=%@ phone=%@ height=%f", name, phone, height);
}
}
线程安全数据库和事务代码:
///=============================================================================
/// @name 数据库多线程安全和事务
///=============================================================================
/**
模拟场景:
甲:500元
乙:1000元
乙给甲转账500元。
*/
#pragma mark - 创建线程安全的数据库
- (void)createAtomicDB {
NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *dbPath = [docDir stringByAppendingPathComponent:@"t_bank.sqlite"];
// 创建队列且默认开发了数据库
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
[queue inDatabase:^(FMDatabase *db) {
// code····Operate db
BOOL flag = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_bank (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, money INTEGER NOT NULL)"];
if (flag) {
DLog(@"create t_bank success");
} else {
DLog(@"create t_bank failed");
}
}];
_queue = queue;
}
// 增
- (IBAction)at_Add:(id)sender {
[_queue inDatabase:^(FMDatabase *db) {
BOOL flag = [db executeUpdate:@"INSERT INTO t_bank (name, money) VALUES (?, ?);", @"甲", @500];
[db executeUpdate:@"INSERT INTO t_bank (name, money) VALUES (?, ?);", @"乙", @1000];
if (flag) {
DLog(@"INSERT INTO t_bank success");
} else {
DLog(@"INSERT INTO t_bank failed");
}
}];
}
// 删
- (IBAction)at_delete:(id)sender {
[_queue inDatabase:^(FMDatabase *db) {
BOOL flag = [db executeUpdate:@"DELETE FROM t_bank"];
if (flag) {
DLog(@"DELETE t_bank success");
} else {
DLog(@"DELETE t_bank failed");
}
}];
}
/**
操作1:乙 (1000 - 500) 扣500
操作2:甲 (500 + 500) 得500
操作1和操作2必须一起成功,称为一个不可分割的工作单元。所以我们用到了事务
若事务内的操作失败了某个,则数据库回滚到事务之前的状态!!
*/
// 改
- (IBAction)at_update:(id)sender {
[_queue inDatabase:^(FMDatabase *db) {
// 开启事务
[db beginTransaction];
// 操作1
BOOL flag1 = [db executeUpdate:@"UPDATE t_bank SET money = 500 WHERE name = '乙';"];
if (flag1) {
DLog(@"flag1UPDATE t_bank success");
} else {
DLog(@"flag1UPDATE t_bank failed");
// 失败回滚
[db rollback];
}
// 操作2
BOOL flag2 = [db executeUpdate:@"UPDATE t_bank SET money = 1000 WHERE name = '甲';"];
if (flag2) {
DLog(@"flag2UPDATE t_bank success");
} else {
DLog(@"flag2UPDATE t_bank failed");
// 失败回滚
[db rollback];
}
// 提交事务
[db commit];
}];
}
// 查
- (IBAction)at_slect:(id)sender {
[_queue inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_bank"];
while ([rs next]) {
DLog(@"name = %@ money = %d", [rs stringForColumn:@"name"], [rs intForColumn:@"money"]);
}
}];
}
参考: