Swift - 第三方SQLite库FMDB使用详解4(实体类与数据库表的关联映射)
作者:hangge | 2019-04-11 08:10
从之前的几篇文章可以发现,使用 FMDB 库时,当我们从数据库中把数据查询出来以后,如果想要映射成实体类对象,就需要遍历查询结果集,一个个给实体类对象赋值。
同样地当实体对象属性修改后,如果想保存到数据库,又需要把对象属性取出来拼成一个 SQL 语句执行。这样就比较麻烦。
下面演示如何直接通过模型操作数据库,而不用再写大量的 sql 语句。
六、实现实体类与数据库表的关联映射
1,创建一个基类(SQLModel)
(1)只要我们让实体类继承 SQLModel,那么它就会自行将实体类跟数据库表作关联映射。之后只要操作实体类对象就可以直接进行数据的读取,修改,新增操作。
(2)SQLModel 其内部实现使用到了 Swift 的反射,通过遍历对象内的所有属性,从而自动拼接成相应的 SQL 语句来执行。
(3)SQLModel 代码如下:
import Foundation protocol SQLModelProtocol {} // 数据库模型(一张表对应一个模型) @objcMembers class SQLModel: NSObject, SQLModelProtocol { // 模型对应的表名(直接使用对应模型类名字) internal var table = "" // 记录每个模式对应的数据表是否已经创建完毕了 private static var verified = [String:Bool]() // 初始化方法 required override init() { super.init() // 自动初始化表名 self.table = type(of: self).table // 自动建对应的数据库表 let verified = SQLModel.verified[self.table] if verified == nil || !verified! { let db = SQLiteManager.shareManger().db var sql = "CREATE TABLE IF NOT EXISTS \(table) (" // Columns let cols = values() var first = true for col in cols { if first { first = false sql += getColumnSQL(column:col) } else { sql += ", " + getColumnSQL(column: col) } } // Close query sql += ")" if db.open() { if db.executeUpdate(sql, withArgumentsIn:[]) { SQLModel.verified[table] = true print("\(table) 表自动创建成功") } } } } // 返回主键字段名(如果模型主键不是id,则需要覆盖这个方法) func primaryKey() -> String { return "id" } // 忽略的属性(模型中不需要与数据库表进行映射的字段可以在这里发返回) func ignoredKeys() -> [String] { return [] } // 静态方法返回表名 static var table:String { // 直接返回类名字 return "\(classForCoder())" } // 删除指定数据(可附带条件) @discardableResult class func remove(filter: String = "") -> Bool { let db = SQLiteManager.shareManger().db var sql = "DELETE FROM \(table)" if !filter.isEmpty { // 添加删除条件 sql += " WHERE \(filter)" } if db.open() { return db.executeUpdate(sql, withArgumentsIn:[]) } else { return false } } // 获取数量(可附带条件) class func count(filter: String = "") -> Int { let db = SQLiteManager.shareManger().db var sql = "SELECT COUNT(*) AS count FROM \(table)" if !filter.isEmpty { // 添加查询条件 sql += " WHERE \(filter)" } if let res = db.executeQuery(sql, withArgumentsIn: []) { if res.next() { return Int(res.int(forColumn: "count")) } else { return 0 } } return 0 } // 保存当前对象数据 // * 如果模型主键为空或者使用该主键查不到数据则新增 // * 否则的话则更新 @discardableResult func save() -> Bool{ let key = primaryKey() let data = values() var insert = true let db = SQLiteManager.shareManger().db if let rid = data[key] { var val = "\(rid)" if rid is String { val = "'\(rid)'" } let sql = "SELECT COUNT(*) AS count FROM \(table) " + "WHERE \(primaryKey())=\(val)" if db.open() { if let res = db.executeQuery(sql, withArgumentsIn: []) { if res.next() { insert = res.int(forColumn: "count") == 0 } } } } let (sql, params) = getSQL(data:data, forInsert:insert) // 执行SQL语句 if db.open() { return db.executeUpdate(sql, withArgumentsIn: params ?? []) } else { return false } } // 删除当天对象数据 @discardableResult func delete() -> Bool{ let key = primaryKey() let data = values() let db = SQLiteManager.shareManger().db if let rid = data[key] { if db.open() { let sql = "DELETE FROM \(table) WHERE \(primaryKey())=\(rid)" return db.executeUpdate(sql, withArgumentsIn: []) } } return false } // 通过反射获取对象所有有的属性和属性值 internal func values() -> [String:Any] { var res = [String:Any]() let obj = Mirror(reflecting:self) processMirror(obj: obj, results: &res) getValues(obj: obj.superclassMirror, results: &res) return res } // 供上方方法(获取对象所有有的属性和属性值)调用 private func getValues(obj: Mirror?, results: inout [String:Any]) { guard let obj = obj else { return } processMirror(obj: obj, results: &results) getValues(obj: obj.superclassMirror, results: &results) } // 供上方方法(获取对象所有有的属性和属性值)调用 private func processMirror(obj: Mirror, results: inout [String: Any]) { for (_, attr) in obj.children.enumerated() { if let name = attr.label { // 忽略 table 和 db 这两个属性 if name == "table" || name == "db" { continue } // 忽略人为指定的属性 if ignoredKeys().contains(name) || name.hasSuffix(".storage") { continue } results[name] = unwrap(attr.value) } } } //将可选类型(Optional)拆包 func unwrap(_ any:Any) -> Any { let mi = Mirror(reflecting: any) if mi.displayStyle != .optional { return any } if mi.children.count == 0 { return any } let (_, some) = mi.children.first! return some } // 返回新增或者修改的SQL语句 private func getSQL(data:[String:Any], forInsert:Bool = true) -> (String, [Any]?) { var sql = "" var params:[Any]? = nil if forInsert { sql = "INSERT INTO \(table)(" } else { sql = "UPDATE \(table) SET " } let pkey = primaryKey() var wsql = "" var rid:Any? var first = true for (key, val) in data { // 处理主键 if pkey == key { if forInsert { if val is Int && (val as! Int) == -1 { continue } } else { wsql += " WHERE " + key + " = ?" rid = val continue } } // 设置参数 if first && params == nil { params = [AnyObject]() } if forInsert { sql += first ? "\(key)" : ", \(key)" wsql += first ? " VALUES (?" : ", ?" params!.append(val) } else { sql += first ? "\(key) = ?" : ", \(key) = ?" params!.append(val) } first = false } // 生成最终的SQL if forInsert { sql += ")" + wsql + ")" } else if params != nil && !wsql.isEmpty { sql += wsql params!.append(rid!) } return (sql, params) } // 返回建表时每个字段的sql语句 private func getColumnSQL(column:(key: String, value: Any)) -> String { let key = column.key let val = column.value var sql = "'\(key)' " if val is Int { // 如果是Int型 sql += "INTEGER" if key == primaryKey() { sql += " PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE" } else { sql += " DEFAULT \(val)" } } else { // 如果是其它类型 if val is Float || val is Double { sql += "REAL DEFAULT \(val)" } else if val is Bool { sql += "BOOLEAN DEFAULT " + ((val as! Bool) ? "1" : "0") } else if val is Date { sql += "DATE" } else if val is NSData { sql += "BLOB" } else { // Default to text sql += "TEXT" } if key == primaryKey() { sql += " PRIMARY KEY NOT NULL UNIQUE" } } return sql } } extension SQLModelProtocol where Self: SQLModel { // 根据完成的sql返回数据结果 static func rowsFor(sql: String = "") -> [Self] { var result = [Self]() let tmp = self.init() let data = tmp.values() let db = SQLiteManager.shareManger().db let fsql = sql.isEmpty ? "SELECT * FROM \(table)" : sql if let res = db.executeQuery(fsql, withArgumentsIn: []){ // 遍历输出结果 while res.next() { let t = self.init() for (key, _) in data { if let val = res.object(forColumn: key) { t.setValue(val, forKey:key) } } result.append(t) } }else{ print("查询失败") } return result } // 根据指定条件和排序算法返回数据结果 static func rows(filter: String = "", order: String = "", limit: Int = 0) -> [Self] { var sql = "SELECT * FROM \(table)" if !filter.isEmpty { sql += " WHERE \(filter)" } if !order.isEmpty { sql += " ORDER BY \(order)" } if limit > 0 { sql += " LIMIT 0, \(limit)" } return self.rowsFor(sql:sql) } }
2,创建具体的实体类
(1)这里我们定义一个用户类(User),只需让它继承 SQLModel 即可。
import Foundation class User: SQLModel { var id:Int = -1 var name:String = "" var age:Int = -1 }
(2)默认情况下,模型里的 id 属性则作为数据库表里的主键。如果想要其它属性作为主键,则需要重写覆盖 primaryKey() 方法:
import Foundation class User: SQLModel { var uid:Int = -1 var name:String = "" var age:Int = -1 // 返回主键名 override func primaryKey() -> String { return "uid" } }
(3)默认情况下,模型里面所有属性都会创建对应的数据库表字段,如果想要某些属性不与数据库表进行映射,则需要重写覆盖 ignoredKeys() 方法:
import Foundation class User: SQLModel { var id:Int = -1 var name:String = "" var age:Int = -1 // 返回忽略的键值 override func ignoredKeys() -> [String] { return ["age"] } }
3,新增数据样例
(1)下面创建 10 个模型对象,并调用 save() 方法保存到库中。
注意:数据库以及数据表我们事先不需要手动创建,模型初始化时会自动判断是否存在对应的表,没有的话会自动创建。
// 创建10条数据 for i in 1..<11 { let user = User() user.name = "测试用户\(i)" user.age = 100 + i user.save() }
(2)打开数据库,可以看到数据都插入成功了。
4,修改数据样例
(1)更新数据同样是使用 save() 方法:
- 如果模型对象主键为空或者该主键在表中查不到对应的记录,则为新增。
- 否则的话则为修改。
(2)下面是一个简答的使用样例:
let user = User() user.id = 2 user.name = "hangge.com" user.age = 0 user.save()
5,删除数据
(1)调用模型对象的 delete() 方法即可将该对象对应的数据删除(内部根据主键来判断)
let user = User() user.id = 2 user.delete()
(2)也可以通过类方法 remove() 来批量删除数据:
// 删除User表所有的数据 User.remove() // 根据指定条件删除User表的数据 User.remove(filter: "id > 5 and age < 107")
6,获取记录数
(1)通过类方法 count() 可以获取数据表中的所有记录数:
let count = User.count() print("记录数:\(count)")
(2)我们也可根据指定条件来获取记录数:
let count = User.count(filter: "id > 5 and age < 107") print("记录数:\(count)")
7,查询数据
(1)通过类方法 rows() 可以获取数据表中的所有数据(会自动转换成对应的模型数组):
// 获取所有数据 let users = User.rows() // 遍历输出结果 for user in users { print(user.id, user.name, user.age) }
(2)rows() 方法还可指定查询条件、排序、以及限制数量。
// 根据条件获取数据 let users = User.rows(filter: "id > 5 and age < 107", order: "age desc", limit: 3) // 遍历输出结果 for user in users { print(user.id, user.name, user.age) }
(3)我们还可以使用 rowsFor() 方法来通过完整的 sql 查询数据(同样也会自动转换成对应的模型数组):
// 通过sql获取数据 let users = User.rowsFor(sql: "select * from User limit 3") // 遍历输出结果 for user in users { print(user.id, user.name, user.age) }
全部评论(0)