swift下FMDB的使用
在IOS中可以使用的存储结构有很多,可以使用文件进行缓存,可以使用useDefault实现简单状态的存储,此外IOS下有一个常用的数据库框架–FMDB,它是对于SQLite的一层封装,可以使用它完成基本的增删查改,也可以对此完成事务,回滚等复杂操作
对于FMDB的集成,我们可以直接使用cocoapod完成集成,就像如下的podfile
target 'YouKu' do
pod 'FMDB'
end
之后,就可以进行操作了,但是由于我使用的是swift,FMDB是使用OC语言封装的库,要使用的话,就应该在项目的根目录下面创建一个bridge.h,在其中导入我们的FMDB
加完以后,我们还需要再info.plist中,进行设置,使得我们的改动内容生效,点击项目–TARGETS–Build Settings,其中有一个设置是swift compiler - General设置其中OC桥接文件为之前自定义的文件
之后的话,就可以直接使用了,我们可以先定义一个公共的操作类,封装数据的初始化和增删查改等操作
先定义好我们的实体类对象
import UIKit
class PersonEntity: NSObject {
var id : Int?;
var name : String?;
var age : Int?;
func initParam(id : Int, name : String, age : Int) {
self.id = id;
self.name = name;
self.age = age;
}
func toString() {
print("用户信息:id = \(id ?? 0) 姓名 = \(name ?? "") 年龄 = \(age ?? -1)");
}
}
首先来说,这个类应该是全局的单例对象,所以说,先设置一个static的对象,让其他的类调用来获取到对象,之后,在使用的时候要先打开db对象,这里需要注意的问题就是,对于这个数据库,我们是通过一个文件路径来生成的,如果原先就存在这个数据库,会直接打开这个文件,如果不存在,就会先创建数据库,在进行开启数据库的操作,值得注意的是,在FMDB中,使用数据库以后要及时关闭数据库,所以来说open和close一般是成对出现的
import UIKit
class FMDBManager: NSObject {
static let shared = FMDBManager();
var dbUrl : String?;
var FMDBEntity : FMDatabase?;
var FMDBQueue : FMDatabaseQueue?;
//打开数据库,也就是创建数据库
func opendb(dbName : String, tableName : String) {
//在doc文件夹下面创建新的文件夹,用来储存数据库
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
let docPath = paths[0] + "/\(dbName)";
self.dbUrl = docPath;
//创建新的数据库
self.FMDBEntity = FMDatabase.init(path: self.dbUrl);
//如果数据库存在,就直接打开,如果不存在,则会现在磁盘上创建数据库,然后再打开
if self.FMDBEntity!.open() {
print("数据库打开成功");
//创建数据表
self.initTable(t_name: tableName);
}else {
print("数据库打开失败");
}
//数据库每次操作完成以后就应该释放资源
self.FMDBEntity!.close();
}
func initTable(t_name : String) {
let sql = "CREATE TABLE IF NOT EXISTS \(t_name)( \n" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, \n" +
"name TEXT, \n" +
"age INTEGER \n" +
"); \n"
if self.FMDBEntity!.executeUpdate(sql, withArgumentsIn: []) {
print("数据表创建成功");
}else {
print("数据表创建失败");
}
}
}
这样的话,我们就完成了数据表的创建,如果要进行增删查改,我们就可以借助于FMDatabase执行对应的sql代码,对于FMDB来说,它和一般的数据库没有区别,所以说,原先使用的sql基本上没有变化,下面举个例子,如果我们要增加一条记录,那么需要执行的sql是
insert into table_name (cloum1, cloum2, cloum3...) values(value1, value2, value3...)
接下来,我们就可以编写一个方法来执行我们的sql,一般来说有两种方式
- 使用完整sql
- 使用占位符,后期填充参数
在这里,可以直接把参数嵌入到自定义的String中,拼接成完整的sql语句用来执行,也可以使用?作为占位符,我使用的是占位符的方式,方法如下
func insert(tableName : String, param : [String : AnyObject]) {
//定义增加的sql
let sql = "insert into \(tableName) (name, age) values(?, ?)";
//开启数据库连接,执行sql语句
if self.FMDBEntity!.open() {
//数据库连接已打开,传入sql和参数
if self.FMDBEntity!.executeUpdate(sql, withArgumentsIn: ["殷素素", 23]) {
print("操作完成");
}else{
print("操作失败");
}
}
self.FMDBEntity!.close();
}
而如果要进行查询操作时,我们就会接触到另外一个对象,那就是FMDB中的结果集FMResultSet,这个东西和jdbc中的结果集是相通的,可以进行逐行的遍历,得到每条数据,然后就可以进行相应的解析,在组合成一个完整的对象,也就是下面的方法
func select(tableName : String, param : [String : AnyObject]) -> [PersonEntity] {
let pId = param["id"];
//定义增加的sql
let sql = "select * from \(tableName) where id = ?";
//开启数据库连接,执行sql语句
var persons : [PersonEntity] = [];
if self.FMDBEntity!.open() {
//数据库连接已打开,传入sql和参数
if let queryResult = self.FMDBEntity?.executeQuery(sql, withArgumentsIn: [pId ?? 0]) {
while queryResult.next() {
let id = queryResult.int(forColumn: "id");
let name = queryResult.string(forColumn: "name") ?? "";
let age = queryResult.int(forColumn: "age");
let p = PersonEntity();
p.initParam(id: Int(id), name: String(name), age: Int(age));
persons.append(p);
}
}
}
self.FMDBEntity!.close();
return persons;
}
下面还有删除和修改的代码,没有什么特别之处了,就不再赘述了
func update(tableName : String, param : [String : AnyObject]) {
//定义修改的sql
let sql = "update \(tableName) set name = ? where id = ?";
//开启数据库连接,执行sql语句
if self.FMDBEntity!.open() {
//数据库连接已打开,传入sql和参数
if self.FMDBEntity!.executeUpdate(sql, withArgumentsIn: ["张老道", 1]) {
print("操作完成");
}else{
print("操作失败");
}
}
self.FMDBEntity!.close();
}
func delete(tableName : String, param : [String : AnyObject]) {
//定义删除的sql
let sql = "delete from \(tableName) where id = ?";
//开启数据库连接,执行sql语句
if self.FMDBEntity!.open() {
//数据库连接已打开,传入sql和参数
if self.FMDBEntity!.executeUpdate(sql, withArgumentsIn: [1]) {
print("操作完成");
}else{
print("操作失败");
}
}
self.FMDBEntity!.close();
}
有点意思的这个数据库里面的事务操作,在FMDB中事务操作依赖于一个队列来实现,那就是FMDatabaseQueue,先贴出来代码,然后再分析一下,先是队列的初始化
//执行之前需要先初始化数据库
func openQueue(dbName : String, tableName : String) {
let path = self.dbUrl ?? "";
if path == "" {
print("要先完成初始化");
self.opendb(dbName: dbName, tableName: tableName);
}
self.FMDBQueue = FMDatabaseQueue.init(path: self.dbUrl);
}
之后执行批量执行的代码
@objc func batchHandle() {
print("批量插入");
FMDBManager.shared.FMDBQueue?.inTransaction({ (db, rollback) in
let trueTable = "t_Student";
let errorTable = "t_Sent";
for i in 40..<50{
var sql = "insert into \(trueTable) (name, age) values(?, ?)";
if i == 45 {
sql = "insert into \(errorTable) (name, age) values(?, ?)";
}
let result : Bool = db.executeUpdate(sql, withArgumentsIn: ["张启山", i]);
if result {
print("插入数据:\(i)");
}else{
print("出现错误,开始回滚");
rollback.pointee = true;
return ;
}
}
})
}
所以来说,进行批量查询的代码关键就在inTransaction这个方法中,如果直接点进去,我们会到达源代码中的这个位置
直接往里跳,会到具体的代码实现
- (void)beginTransaction:(FMDBTransaction)transaction withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
FMDBRetain(self);
dispatch_sync(_queue, ^() {
BOOL shouldRollback = NO;
switch (transaction) {
case FMDBTransactionExclusive:
[[self database] beginTransaction];
break;
case FMDBTransactionDeferred:
[[self database] beginDeferredTransaction];
break;
case FMDBTransactionImmediate:
[[self database] beginImmediateTransaction];
break;
}
block([self database], &shouldRollback);
if (shouldRollback) {
[[self database] rollback];
}
else {
[[self database] commit];
}
});
FMDBRelease(self);
}
//下面这两段源代码是FMDatabase.m文件里面的函数
- (BOOL)rollback {
BOOL b = [self executeUpdate:@"rollback transaction"];
if (b) {
_isInTransaction = NO;
}
return b;
}
- (BOOL)commit {
BOOL b = [self executeUpdate:@"commit transaction"];
if (b) {
_isInTransaction = NO;
}
return b;
}
不难看出,接下来,我们会跳转到beginTransaction方法,那这个方法执行内容又是什么呢,其实就只有一行代码
- (BOOL)beginTransaction {
BOOL b = [self executeUpdate:@"begin exclusive transaction"];
if (b) {
_isInTransaction = YES;
}
return b;
}
就是执行了begin exclusive transaction,然后由于我们实现了源代码里面的block,会在这条执行以后粘贴上十条sql,然后根据shouldRollback的值决定最后的代码,我们在自己的代码执行时,通过判断每一插入语句的执行结果,决定返回的布尔值,然后如果每条代码都能执行,就提交commit,所有代码提交生效,否则就提交rollback,所有代码回滚,完成了事务的功能
参考文章
第一篇文章
第二篇文章
第三篇文章
对于更多的FMDB和Model封装操作,可以参考下面这篇文章,所的也很丰富,有兴趣的可以自己研究一下
实体类与数据库表的关联映射