WCDB使用看这篇就够了
我这儿WCDB使用的版本是2.0.4,我直接贴代码,可以直接看demo,详细用法和封装都写在demo里了,我是demo,查看数据库工具可以用这个,我是工具,提取码: 7jx7,工具下载下来可能安装可能显示已损坏,设置-安全性与隐私-通用 里设置允许任何来源,没那个选项就命令行搞定,复制下面语句命令行执行再次查看。
sudo spctl --master-disable
WCDB增删改查
/*
* 这儿对WCDB的操作打开了监听日志的,可留意控制台打印信息
* 对WCDB的任何操作都依赖于模型绑定,模型首先要遵循TableCodable协议,需要存储的值需要在enum CodingKeys: String, CodingTableKey{}里面case 记录,static let objectRelationalMapping = TableBinding(CodingKeys.self) {}方法里设置你对数据库存储值的设定
* WCDB数据库一般情况下不需要开发者手动调用开关。操作时自动打开,当没有指向 Database 所共享的 Core 时,数据库会自动关闭,并回收内存
* 具体更多信息自己阅读WCDB文档
*/
//MARK: - 增
private func addMenuItems() -> UIMenu {
let addAction1 = UIAction.init(title: "insert") { _ in
let object = Sample()
object.identifier = 1
object.description = "abc"
//纯插入操作,由于设置了identifier为主键,所以identifier必须唯一,不然插入必失败并打印错误
try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
}
let addAction2 = UIAction.init(title: "insertOrReplace") { _ in
let object = Sample()
object.identifier = 1
object.description = "abcd"
//当主键重复时即为更新,不重复时即直接插入数据
try? DBManager.shared.db.insertOrReplace(object, intoTable: "\(Sample.self)")
}
let addAction3 = UIAction.init(title: "insertOrIgnore") { _ in
let object = Sample()
object.identifier = 1
object.description = "abcdefg"
//当出现主键重复时,直接忽略此操作,不重复时才插入
try? DBManager.shared.db.insertOrIgnore(object, intoTable: "\(Sample.self)")
}
let addAction4 = UIAction.init(title: "带自定义对象insert") { _ in
let object = Sample()
//由于设置了主键identifier为自增,这儿identifier会视数据库中的最大值自增
object.description = "自定义对象存储"
guard let data = try? JSONEncoder().encode(["variable1": "1","variable2": "2"]) else {return}
let cuModel = Customer(with: Value(data))
object.myClass = cuModel
//这儿的Customer自定义对象我们使用二进制存储,存储自定义对象只需要遵循ColumnCodable协议,且实现init?(with Value)和archivedValue方法,这两个方法相当于是解归档
try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
}
let addAction5 = UIAction.init(title: "Sample设置了description不为空而传入空值") { _ in
let object = Sample()
object.identifier = 10
//Sample设置了description不可为空,这样插入会失败且报错
try? DBManager.shared.db.insert(object, intoTable: "\(Sample.self)")
}
return UIMenu.init(children: [addAction1,addAction2,addAction3,addAction4,addAction5])
}
//MARK: - 删
private func deleteMenuItems() -> UIMenu {
let deleteAction1 = UIAction.init(title: "delete") { _ in
//delete(fromTable table: String,where condition: Condition? = nil,orderBy orderList: [OrderBy]? = nil,limit: Limit? = nil,offset: Offset? = nil),这五个组合起来可以理解为:将 table 表内,满足 condition 的数据,按照 orderList 的方式进行排序,然后从头开始第 offset 行数据后的 limit 行数据删除,各参数代表的含义:table:表名,condition:条件,这儿可以多个条件合并,orderList:排序规则,这儿是数组,可以有多个列的排序规则,limit:无offset时理解为前 limit 行,有offset时理解为后 limit 行,offset:前 offset 行
try? DBManager.shared.db.delete(fromTable: "\(Sample.self)",
where: Sample.Properties.identifier > 2 && Sample.Properties.description == "abc",
orderBy: [Sample.Properties.identifier.asOrder().order(.descending)],
limit: 2,offset: 3)
}
return UIMenu.init(children: [deleteAction1])
}
//MARK: - 改
private func changeMenuItems() -> UIMenu {
//propertyConvertibleList、condition、limit 和 offset 前面介绍过了,两个更新不同的在with参数,on后的参数代表需要更新的列
let changeAction1 = UIAction.init(title: "基于对象的更新(update with object)") { _ in
let object = Sample()
object.description = "byObject"
try? DBManager.shared.db.update(table: "\(Sample.self)",
on: [Sample.Properties.description],
with: object,
where: Sample.Properties.identifier > 1)
}
let changeAction2 = UIAction.init(title: "基于值的更新(update with row)") { _ in
let row: [ColumnCodable] = ["byRow"]
//当on后的参数包含myClass而未设定myClass的值时,会置空
try? DBManager.shared.db.update(table: "\(Sample.self)",
on: [Sample.Properties.description,Sample.Properties.myClass],
with: row,
where: Sample.Properties.identifier == 10000)
}
return UIMenu.init(children: [changeAction1,changeAction2])
}
//MARK: - 查
/*
* 若是部分列查询只获取了 identifier 字段,而没有获取 description 的值。这就可能与 Swift 本身存在冲突。 Swift 规定了对象创建时,必须初始化所有成员变量。而进行对象部分查询时,则可能出现某部分变量没有变查询,因此无法初始化的情况。因此,对于可能不被查询的成员变量,应将其类型定义为可选值。 对于 Sample 类中,"getObjects" 接口虽然没有获取 description 的值,但由于 description 是 String? 类型,因此不会出错。 而将`var description: String?` 改为 `var description: String`就会出错,这儿使用部分查询时模型绑定里的值一定要都设置为可选值
* 只有getObjects和getObject为对象查询,其余都是值查询
* "getRows" 接口获取整个矩阵的所有内容,即返回值为二维数组。
* "getRow" 接口获取某一横行的数据,即返回值为一维数组。
* "getColumn" 接口获取某一纵列的数据,即返回值为一维数组。
* "getDistinctColumn" 与 "getColumn" 类似,但它会过滤掉重复的值。
* "getValue" 接口获取矩阵中某一个格的内容。
* "getDistinctValue" 与 "getValue" 类似,但它会过滤掉重复的值。
*/
private func checkMenuItems() -> UIMenu {
let checkAction1 = UIAction.init(title: "getObjects(全部列的查询)") { _ in
do {
let allObjects: [Sample] = try DBManager.shared.db.getObjects(on: Sample.Properties.all, fromTable: "\(Sample.self)")
print("getObjects(全部列的查询)",allObjects)
} catch {
}
}
let checkAction2 = UIAction.init(title: "getObject(全部列的查询)") { _ in
do {
//"getObject" 等价于 limit: 1 时的 "getObjects" 接口
let object: Sample? = try DBManager.shared.db.getObject(on: Sample.Properties.all, fromTable: "\(Sample.self)")
if let obj = object {
print("getObject(全部列的查询)",obj.identifier,obj.description,obj.myClass)
} else {
print("getObject(全部列的查询)obj为空")
}
} catch {
}
}
let checkAction3 = UIAction.init(title: "getObjects(部分列的查询)") { _ in
do {
let allObjects: [Sample] = try DBManager.shared.db.getObjects(on: [Sample.Properties.identifier,Sample.Properties.myClass], fromTable: "\(Sample.self)")
print("getObjects(部分列的查询)",allObjects)
} catch {
}
}
let checkAction4 = UIAction.init(title: "getRows(全部列的查询)") { _ in
do {
let allRows = try DBManager.shared.db.getRows(on: Sample.Properties.all, fromTable: "\(Sample.self)")
print("getRows(全部列的查询)",allRows)
//row column 代表查询到的值里面第 row 行 第 column 列
print("getRows(全部列的查询)",allRows[row: 0, column: 0].int64Value)
} catch {
}
}
let checkAction5 = UIAction.init(title: "getRow(全部列的查询)") { _ in
do {
//若是带 offset 参数表示查询第 offset+1 行
let row = try DBManager.shared.db.getRow(on: Sample.Properties.all, fromTable: "\(Sample.self)",offset: 1)
print("getRow(全部列的查询)",row)
print("getRow(第二行第1列值的查询)",row[1].stringValue)
} catch {
}
}
let checkAction6 = UIAction.init(title: "getColumn(部分列的查询)") { _ in
do {
//获取 description 列
let descriptionColumn = try DBManager.shared.db.getColumn(on: Sample.Properties.description, fromTable: "\(Sample.self)")
print("getColumn(description列的查询)",descriptionColumn)
print("getColumn(description列第0个的查询)",descriptionColumn[0].stringValue)
} catch {
}
}
let checkAction7 = UIAction.init(title: "getDistinctColumn(部分列的查询)") { _ in
do {
//获取 description 列
let distinctDescriptionColumn = try DBManager.shared.db.getDistinctColumn(on: Sample.Properties.description, fromTable: "\(Sample.self)")
print("getDistinctColumn(description列的查询)",distinctDescriptionColumn)
print("getDistinctColumn(description列第0个的查询)",distinctDescriptionColumn[0].stringValue)
} catch {
}
}
let checkAction8 = UIAction.init(title: "getValue(部分列的查询)") { _ in
do {
//获取 description 列,无offset参数代表第一行,有即 offset+1行
let value = try DBManager.shared.db.getValue(on: Sample.Properties.description, fromTable: "\(Sample.self)")
print("getValue(description列第一行的查询)",value)
print("getValue(description列第一行值的查询)",value.stringValue)
} catch {
}
}
let checkAction9 = UIAction.init(title: "getValue(identifier最大值的查询)") { _ in
do {
//获取 identifier最大值
let value = try DBManager.shared.db.getValue(on: Sample.Properties.identifier.max(), fromTable: "\(Sample.self)")
print("getValue(identifier最大值的查询)",value)
print("getValue(identifier最大值的查询)",value.int64Value)
} catch {
}
}
let checkAction10 = UIAction.init(title: "getDistinctValue(不重复的description查询)") { _ in
do {
//获取不重复的description的值
let value = try DBManager.shared.db.getDistinctValue(on: Sample.Properties.description, fromTable: "\(Sample.self)")
print("getDistinctValue(不重复的description查询)",value)
print("getDistinctValue(不重复的description查询)",value.stringValue)
} catch {
}
}
let checkAction11 = UIAction.init(title: "联表查询prepareMultiSelect(需要先执行可中断事务,在另一张sampleTable插入些数据)") { _ in
do {
//这儿联表查询需要查哪些列的只能单独列出来,不能用.all
let multiSelect = try DBManager.shared.db.prepareMultiSelect(on: [Sample.Properties.identifier.in(table: "\(Sample.self)"),
Sample.Properties.description.in(table: "\(Sample.self)"),
Sample.Properties.identifier.in(table: "sampleTable"),
Sample.Properties.description.in(table: "sampleTable")],
fromTables: ["\(Sample.self)","sampleTable"])
.where(Sample.Properties.identifier.in(table: "\(Sample.self)") == Sample.Properties.identifier.in(table: "sampleTable"))
while let multiObject = try multiSelect.nextMultiObject() {
let sample = multiObject["\(Sample.self)"] as? Sample
let otherSample = multiObject["sampleTable"] as? Sample
print("联表查询",sample,otherSample)
}
} catch {
}
}
return UIMenu.init(children: [checkAction1,checkAction2,checkAction3,checkAction4,checkAction5,checkAction6,checkAction7,checkAction8,checkAction9,checkAction10,checkAction11])
}
//MARK: - 表
/* 表相当于指定了表名和模型绑定类的 Database,其实质只是后者的简化版。增删查改中提到的所有接口Table都具备,而且这些接口调用时都不需要再传表名和 ORM 类型,因为执行数据读写时Table使用起来比Database更加简洁,而且也有利于以表为单位来管理数据读写逻辑,所以WCDB推荐尽量使用Table来进行数据读写。
*/
private func tableMenuItems() -> UIMenu {
//下面增删改查分别写个示例
let table = DBManager.shared.db.getTable(named: "\(Sample.self)",of: Sample.self)
let tableAction1 = UIAction.init(title: "增") { _ in
let object = Sample()
object.identifier = 55
object.description = "增"
try? table.insert(object)
}
let tableAction2 = UIAction.init(title: "删") { _ in
try? table.delete(limit: 1)
}
let tableAction3 = UIAction.init(title: "改") { _ in
let object = Sample()
object.description = "改"
try? table.update(on: Sample.Properties.description, with: object,where: Sample.Properties.identifier > 55)
}
let tableAction4 = UIAction.init(title: "查") { _ in
do {
let objects:[Sample] = try table.getObjects(on: Sample.Properties.all)
print("表查询",objects)
} catch {
}
}
return UIMenu.init(children: [tableAction1,tableAction2,tableAction3,tableAction4])
}
//MARK: - 事务
/*
* 事务一般用于 提升性能 和 保证数据原子性。Database 和 Table 都能直接发起事务,也可以通过 Transaction 更好地控制事务
* 事务提升性能的实质是批量处理
* 在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后 和 取出数据之前 的瞬间,则 getObjects() 无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。而事务可以保证一段操作的原子性
*/
private func transactionMenuItems() -> UIMenu {
let table = DBManager.shared.db.getTable(named: "\(Sample.self)",of: Sample.self)
let transAction1 = UIAction.init(title: "多个对象单独插入") { _ in
let object = Sample()
object.description = "多个对象单独插入"
let objects = Array(repeating: object, count: 2000)
for object in objects {
try? table.insert(object)
}
}
let transAction2 = UIAction.init(title: "多个对象事务插入") { _ in
let object = Sample()
object.description = "多个对象事务插入"
let objects = Array(repeating: object, count: 2000)
//insert(objects:) 接口内置了事务,并对批量数据做了针对性的优化,性能更好
try? DBManager.shared.db.run(transaction: { _ in
for object in objects {
try? table.insert(object)
}
})
}
//在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后 和 取出数据之前 的瞬间,则 getObjects() 无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。
let transAction3 = UIAction.init(title: "插入查询不写在事务中") { _ in
DispatchQueue(label: "other thread").async {
try? table.delete()
}
let object = Sample()
object.description = "不写在事务中"
try? table.insert(object)
do {
let objects = try table.getObjects(on: Sample.Properties.all)
print("插入查询不写在事务中",objects.count) // 值不固定说明先后顺序不确定
} catch {
}
}
let transAction4 = UIAction.init(title: "插入查询都写在事务中") { _ in
DispatchQueue(label: "other thread").async {
try? table.delete()
try? DBManager.shared.db.run(transaction: { _ in
let object = Sample()
object.description = "都写在事务中"
try? table.insert(object)
do {
let objects = try table.getObjects(on: Sample.Properties.all)
print("插入查询都写在事务中",objects.count) // 输出1
} catch {
}
})
}
}
//WCDB Swift 提供了四种事务,普通事务、可控事务、嵌入事务和可中断事务
//上面的transAction1和transAction2皆为普通事务
let transAction5 = UIAction.init(title: "可控事务") { _ in
let object = Sample()
object.description = "可控事务"
let objects = Array(repeating: object, count: 10)
//由于事务是批量处理,所以i == 3返回 false即回滚了数据,也就一个数据都没插入
try? DBManager.shared.db.run(controllableTransaction: { _ in
for i in 0...objects.count-1 {
let obj = objects[i]
if i == 3 {
return false
}
try? table.insert(obj)
}
return true
})
}
let transAction6 = UIAction.init(title: "嵌入事务") { _ in
let object = Sample()
object.description = "嵌入事务"
let objects = Array(repeating: object, count: 10)
try? DBManager.shared.db.run(transaction: { _ in
try? DBManager.shared.db.run(controllableTransaction: { _ in
for i in 0...objects.count-1 {
let obj = objects[i]
if i == 3 {
return false
}
try? table.insert(obj)
}
return true
})
})
}
//在需要对数据库进行大量数据更新的场景,我们的开发习惯一般是将这些更新操作统一到子线程处理,这样可以避免阻塞主线程,影响用户体验。为了解决大事务会阻塞主线程的问题,WCDB 才加入了可中断事务。可中断事务把一个流程很长的事务过程看成一个循环逻辑,每次循环执行一次短时间的DB操作。操作之后根据外部传入的参数判断当前事务是否可以结束,如果可以结束的话,就直接Commit Transaction,将事务修改内容写入磁盘。如果事务还不可以结束,再判断主线程是否因为当前事务阻塞,没有的话就回调外部逻辑,继续执行后面的循环,直到外部逻辑处理完毕。如果检测到主线程因为当前事务阻塞,则会立即 Commit Transaction,先将部分修改内容写入磁盘,并唤醒主线程执行DB操作。等到主线程的DB操作执行完成之后,在重新开一个新事务,让外部可以继续执行之前中断的逻辑。
let transAction7 = UIAction.init(title: "可中断事务") { _ in
var objects: [Sample] = []
for i in 0..<100 {
let obj = Sample()
obj.identifier = i
obj.description = "可中断事务\(i)"
objects.append(obj)
}
DispatchQueue(label: "other thread").async {
do {
var index = 0
try DBManager.shared.db.run(pausableTransaction: { handle, stop, isNewTransaction in
// isNewTransaction表示第一次执行,或者事务在上次循环结束之后被中断提交了
if isNewTransaction {
//新事务先建一下表,避免事务被中断之后,表已经被其他逻辑删除
try handle.create(table: "sampleTable", of: Sample.self)
}
//写入一个对象,这里还可以用PreparedStatement来减少SQL解析的耗时
try handle.insert(objects[index], intoTable: "sampleTable")
index += 1
//给stop赋值成true表示事务结束
stop = index >= (objects.count-3)
})
} catch {
print("Transaction failed with error: \(error)")
}
}
}
return UIMenu.init(children: [transAction1,transAction2,transAction3,transAction4,transAction5,transAction6,transAction7])
}
//MARK: - 语言集成查询
/*
* 语言集成查询使得开发者能够通过 Swift 的语法特性去完成 SQL 语句。
*/
private func integratedQueryMenuItems() -> UIMenu {
let integratedQueryAction1 = UIAction.init(title: "增") { _ in
let statementInsert = StatementInsert().insert(intoTable: "\(Sample.self)").columns(Sample.Properties.identifier).values(99)
print(statementInsert.description) // 输出 "INSERT INTO Sample(identifier) VALUES(99)"
}
let integratedQueryAction2 = UIAction.init(title: "删") { _ in
let statementInsert = StatementDelete().delete(from: "\(Sample.self)").where(Sample.Properties.identifier > 5)
print(statementInsert.description) // 输出 "DELETE FROM Sample WHERE id > 5"
}
let integratedQueryAction3 = UIAction.init(title: "改") { _ in
let statementInsert = StatementUpdate().update(table: "\(Sample.self)").where(Sample.Properties.identifier == 1).set(Sample.Properties.description).to("语言集成查询改值")
print(statementInsert.description) // 输出 "UPDATE Sample SET description = '语言集成查询改值' WHERE id == 1"
}
let integratedQueryAction4 = UIAction.init(title: "查") { _ in
let statementInsert = StatementSelect().select(Sample.Properties.description).from("\(Sample.self)").where(Sample.Properties.identifier > 5)
print(statementInsert.description) // 输出 "SELECT description FROM Sample WHERE id > 5"
}
return UIMenu.init(children: [integratedQueryAction1,integratedQueryAction2,integratedQueryAction3,integratedQueryAction4])
}
//MARK: - Other
/*
* 数据库升级
*
* WCDB的数据库升级很简单,我们知道不销毁表的情况下,无法对列直接进行删除,所以WCDB做了这样的处理
* 直接在模型绑定里进行列的是否存储操作,新加列可以在case 里加上,当db.create(table:of:)调用时会自动在表里新增列,'删除'列可在case 里删除,当db.create(table:of:)调用时会自动在表里忽略此列,但是表里此列并没有删除,且以前此列的值不会删除
*/
/*
* 数据库操作
*
* database.purge()即回收 database 数据库中暂不使用的内存
* Database.purge()即回收所有已创建的数据库中暂不使用的内存
* 在 iOS 平台上,当内存不足、收到系统警告时,WCDB Swift 会自动调用 Database.purge() 接口以减少内存占用
* 某些情况下,开发者需要确保数据库完全关闭后才能进行操作,如移动文件操作
* 可以使用以下方式保证是在WCDB数据库关闭后做的操作
* try? database.close(onClosed: {
* try database.moveFiles(toDirectory: otherDirectory)
* })
*/
/*
* 文件与代码模版
*
* 模型绑定的大部分都是格式固定的代码,因此,WCDB Swift 提供了文件模版和代码模版两种方式,以简化模型绑定操作。 文件和代码模版都在源代码的 tools/templates 目录下
* 这儿我们使用文件模版,首先获取 WCDB 的 Github 仓库,在命令行如下操作:
* cd path-to-your-wcdb-dir/tools/templates //进入wcdb项目里找到tools->templats目录,拖进去
* sh install.sh //这儿是把文件工具导入Xcode创建新文件里
* 文件模版安装完成后,在 Xcode 的菜单 File -> New -> File... 中创建新文件,通用数据模版选择 TableCodable。 在弹出的菜单中输入文件名,并选择 Language 为 Swift 即可。
* 自定义类型模版选择 ColumnCodable
*/
WCDB推荐使用表操作,我有封装,可直接使用
class DBManager: NSObject {
private let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0].appending("/myWCDB.db")
private override init() {
db = Database(at: path)
print("WCDB数据库地址",path)
//全局性能监控
Database.globalTrace { tag, path, handleId, sql, cost in
print("WCDB数据库性能指标: tag \(tag) id \(handleId) at path \(path) takes \(cost) seconds to execute sql \(sql)");
}
//全局错误监控
Database.globalTrace(ofError: { (error: WCDBError) in
#if DEBUG
assert(error.level != .Fatal)
#endif
if error.level == .Ignore {
print("可忽略WCDB数据库信息",error)
} else {
print("WCDB数据库错误",error)
}
})
db.setNotification { corruptedDatabase in
print("Database is corrupted: tag \(corruptedDatabase.tag ?? 0), path \(corruptedDatabase.path)")
//WCDB 检测到损坏之后,isAlreadyCorrupted会始终返回 YES
print("WCDB数据库有损坏",corruptedDatabase.isAlreadyCorrupted())// 输出1
}
}
static let shared:DBManager = DBManager.init()
public var db:Database!
//MARK: - 创建表
public func createTable<T:TableCodable>(name:String? = nil, model:T.Type) {
try? db.create(table: name ?? "\(T.self)", of: T.self)
}
//MARK: - 增
public func insertOrReplace<T:TableCodable>(_ objects:T..., tableName:String? = nil, on propertyConvertibleList: [PropertyConvertible]? = nil) {
let table = db.getTable(named: tableName ?? "\(T.self)", of: T.self)
try? table.insertOrReplace(objects, on: propertyConvertibleList)
}
public func insertOrIgnore<T:TableCodable>(_ objects:T..., tableName:String? = nil, on propertyConvertibleList: [PropertyConvertible]? = nil) {
let table = db.getTable(named: tableName ?? "\(T.self)", of: T.self)
try? table.insertOrIgnore(objects, on: propertyConvertibleList)
}
//MARK: - 删
public func delete<T:TableCodable>(_ model:T.Type,
tableName:String? = nil,
where condition: Condition? = nil,
orderBy orderList: [OrderBy]? = nil,
limit: Limit? = nil,
offset: Offset? = nil) {
let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
try? table.delete(where: condition, orderBy: orderList, limit: limit, offset: offset)
}
//MARK: - 改
public func update<T:TableCodable>(tableName:String? = nil,
on propertyConvertibleList: PropertyConvertible...,
with object: T,
where condition: Condition? = nil,
orderBy orderList: [OrderBy]? = nil,
limit: Limit? = nil,
offset: Offset? = nil) {
let table = db.getTable(named: tableName ?? "\(T.self)",of: T.self)
try? table.update(on: propertyConvertibleList, with: object, where: condition, orderBy: orderList, limit: limit, offset: offset)
}
//MARK: - 查
public func getObjects<T:TableCodable>(_ model:T.Type,
tableName:String? = nil,
where condition: Condition? = nil,
orderBy orderList: [OrderBy]? = nil,
limit: Limit? = nil,
offset: Offset? = nil) -> [T] {
let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
do {
let objects:[T] = try table.getObjects(on: T.Properties.all, where: condition, orderBy: orderList, limit: limit, offset: offset)
return objects
} catch {
return []
}
}
public func getObject<T:TableCodable>(_ model:T.Type,
tableName:String? = nil,
where condition: Condition? = nil,
orderBy orderList: [OrderBy]? = nil,
offset: Offset? = nil) -> T? {
let table = db.getTable(named: tableName ?? "\(model.self)",of: model.self)
do {
let object:T? = try table.getObject(on: T.Properties.all, where: condition, orderBy: orderList, offset: offset)
return object
} catch {
return nil
}
}
}
WCDB通用模型
/*
* 模型绑定可参照SampleORM,模型绑定类型可为class,struct,enum
*
* 字段映射的类型,并非所有类型的变量都支持被绑定为字段。WCDB Swift 内建了常用类型的支持,包括:
* 32 位整型 Bool, Int, Int8, Int16, Int32, UInt, UInt8, UInt16, UInt32
* 64 位整型 Int64, UInt64, Date
* 浮点型 Float, Double
* 字符串类型 String, URL
* 二进制类型 Data, Array, Dictionary, Set
* 其中 Date 以时间戳的形式存储, Array、Dictionary、Set 以 JSON 的形式存储。
* 对于没有内建支持的类型,可以手动为其添加支持,参照Customer
*
*/
final class Sample: TableCodable {
//模型绑定类型最好都设置为可选值,不然部分查询容易出错
var identifier: Int? = nil
var description: String? = nil
var myClass: Customer? = nil
var multiUniquePart1: Int? = nil
var multiUniquePart2: Int? = nil
//这一块管WCDB在数据库需要存储的值
enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
//这样写是重命名数据库中存储的列名,就是实际值是id不是identifier
case identifier = "id"
case description
case myClass
case multiUniquePart1
case multiUniquePart2
//这一块是配置数据库存储值的设定
static let objectRelationalMapping = TableBinding(CodingKeys.self) {
/*
* BindColumnConstraint(
* _ codingKey: CodingTableKeyType,// 对应字段的枚举
* isPrimary: Bool = false, // 该字段是否为主键。字段约束中只能同时存在一个主键
* orderBy term: OrderTerm? = nil, // 当该字段是主键时,存储顺序是升序还是降序
* isAutoIncrement: Bool = false, // 当该字段是主键时,其是否支持自增。只有整型数据可以定义为自增。
* onConflict conflict: Conflict? = nil, // 当该字段是主键时,若产生冲突,应如何处理
* isNotNull: Bool = false, // 该字段是否可以为空
* isUnique: Bool = false, // 该字段是否可以具有唯一性
* defaultTo defaultValue: ColumnDef.DefaultType? = nil // 该字段在数据库内使用什么默认值)
*/
//设置列的配置,WCDB里表主键只能设置一个
BindColumnConstraint(identifier, isPrimary: true)
//设置索引,索引名为表名+subfix(即这儿_uniqueIndex), isUnique:是否唯一(唯一索引就是字段的值不能有重复的两行出现)
//列同时为主键和列的唯一索引,主键优先级大于唯一索引
BindIndex(identifier, namedWith: "_uniqueIndex",isUnique: true)
//对于需要特别指明索引存储顺序的字段,可以通过 asIndex(orderBy:) 函数指定
BindIndex(description.asIndex(orderBy: .descending), namedWith: "_descendingIndex")
//对于由多个字段组成的联合索引,BindIndex后面可以指定多个字段
BindIndex(identifier, description.asIndex(orderBy: .descending), namedWith: "_multiIndex")
//联合主键约束
// BindMultiPrimary(multiUniquePart1,multiUniquePart2.asIndex(orderBy: .descending))
//联合唯一约束
// BindMultiUnique(multiUniquePart1,multiUniquePart2)
//检查约束,有此约束,必须满足里面条件的数据才可插入,否则报错
// BindChecks {
// multiUniquePart2 > 3
// }
//设置两表的联合外键,用于联表查询
//点击了"可中断事务"后会有sampleTable,然后可以使用以下sql语句查询
//select sampleTable.id as sample_id, sampleTable.description as sample_des from sampleTable join Sample on Sample.id = sampleTable.id
// BindForeginKey(identifier, foreignKey: ForeignKey.init().references(with: "sampleTable").columns(Sample.Properties.identifier))
}
}
//当主键需要自动递增时需设置下面两个属性
//用于定义是否使用自增的方式插入
var isAutoIncrement: Bool = false//这儿一定要设置为false,设置了初始值false一样递增,若是设置了true当插入相同主键时,会crash
//用于获取自增插入后的主键值
var lastInsertedRowID: Int64 = 0
}
自定义模型
class Customer: ColumnCodable {
var variable1: String? = nil
var variable2: String? = nil
static var columnType: ColumnType {
return .BLOB
}
required init?(with value: Value) {
let data = value.dataValue
guard data.count > 0 else {
return nil
}
guard let dictionary = try? JSONDecoder().decode([String: String].self, from: data) else {
return nil
}
variable1 = dictionary["variable1"] ?? ""
variable2 = dictionary["variable2"] ?? ""
}
func archivedValue() -> Value {
if let data = try? JSONEncoder().encode(["variable1": variable1,"variable2": variable2]) {
return Value(data)
}
return Value(nil)
}
}