FMDB 使用的一些注意点

159 阅读3分钟

需求

本篇讲一下 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 语句时字段不会填错
    • image.png
  • 建库用客户端复制建表语句
    • 可以在数据库客户端先设计创建好表,然后复制建表语句使用,结构更清晰.
    • image.png

如何保证线程安全

  • 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 的使用,本篇就水到这儿吧