需求
本篇讲一下 FMDB的一些tips,基本使用就不提了,现在网上gpt 什么的一大堆.
注意点
- 开启数据库
- 数据库建表怎么写
- 如何保证线程安全
- 如何解决事务内开启事务的问题
问题解决
开启数据库
建库,建表等都是同步行为,所以在准备建表,接受消息之前执行即可
public func open(databaseName: String) {
// "test_10001.db"
let dir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? ""
let fullPath = "\(dir)/\(databaseName).db"
Self.shared.databaseName = databaseName
// 创建数据库
Self.shared.dbQueue = FMDatabaseQueue(path: fullPath)
// 后续可以执行表操作
}
数据库建表怎么写
- 字段用变量控制,保证在写 sql 语句时字段不会填错
- 建库用客户端复制建表语句
- 可以在数据库客户端先设计创建好表,然后复制建表语句使用,结构更清晰.
如何保证线程安全
- FMDB 内置了一个
FMDatabaseQueue
,使用串行队列执行任务,使用其inDatabase
,inTransaction
等api,都会在其队列内执行,使其保证数据安全
如何解决事务内开启事务的问题
这是在使用初期很可能遇到的问题,在对象型数据库中也十分常见,start a transaction within a transaction
,嵌套事务,sqlite 是不支持这一行为的,所以我们必须要避免这一问题,如果有联动的表数据修改,比如聊天业务中,插入一条消息,会话表里响应的会话需要插入,未读数要+1等等,如果在事务中开启,这块儿就需要注意,因为你操作的是FMDB,你需要对整个数据库语句操作有一个清晰的流程走向,在整个事务中,你需要仅使用inTransaction,或者 inDatabase传递的FMDatabase来操作 sql 语句执行,中间最好不要有异步的一些通知行为,因为你不能保证这些异步行为会被外部拿去做什么业务内容,你需要保证在 sql整个闭包内容中,不要再调用到inTransaction这种行为.当然,因为queue 的操作逻辑是同步执行的,你可以大胆的在闭包后去执行这些通知行为,这样是不会引起嵌套事务的.
比如我下面的这段逻辑.
let isSuccess = excutor.inTransaction { db in
// 插入消息
_ = try exopet_tmExcutor.insertOrUpdate(db: db, msgId: message.message.msgId, queryBlock: { old in
isUpdateMessage = old != nil
let s = old ?? message.message
if isUpdateMessage {
// 这里可以做后续的修改敏感content的业务功能
s.sendStatus = message.message.sendStatus
s.status = message.message.status
s.content = message.message.content
s.ext = message.ext.toJSONString()
}
return s
})
// 插入session
_ = try exopet_tsExcutor.insertOrUpdate(db: db, sessionId: message.message.sessionId) { old in
let isInsertSession = old == nil
let s = old ?? ExopetChatSessionEntity()
// 会话为新会话
// 1.未读数 发送方 0
// 2.未读数 接收方 1
// 会话为老会话
if isInsertSession {
s.sessionId = message.message.sessionId
s.unread = message.isSender ? 0 : 1
} else {
if isUpdateMessage {
// 更新消息的会话不做处理
} else {
// 插入消息, 接收方
if message.isReceiver {
s.unread += 1
}
}
}
// 标为热聊
if message.isFromUser {
s.contactLevel = message.isSender ? 2 : message.ext.contactLevel
// 更新基础信息
s.nickname = user.nickname
s.headUrl = user.headUrl
// 预览文本
s.lastMsg = message.exopet_sessionContent
s.timestamp = message.message.timestamp
} else {
// 系统级别的全部设置为2
s.contactLevel = 2
// 预览文本
s.lastMsg = message.exopet_sessionContent
}
return s
}
// 更新未读
_ = try exopet_tsExcutor.updateStrangerUnread(db: db, strangerId: ExopetPalAppValue.chatStrangerId)
}
if isUpdateMessage {
// 通知消息状态更新
ExopetDatabaseTool.Notif.exopet_messageUpdate.onNext(message)
}
if isSuccess {
ExopetDatabaseTool.Notif.expet_sessionUpdate.onNext(())
}
所以,你可以细化对 sql 的操作语句,一部分方法为传入 db 操作,这样就可以完全保证不会产生任何有问题的行为.
PS
移动端业务大多也不用靠数据库活着,所以,数据库业务也不会太过繁重,像那种连个四五张表查的行为,我目前也没怎么碰到过,所以说,其实虽然使用 FMDB 这种,可精确操控sql,但是,对象型的数据库显然更轻松一些.后面有机会,可以介绍一下 RealmSwift 的使用,本篇就水到这儿吧