【iOS(swift)笔记-11】App版本升级时本地数据库sqlite更新逻辑

/*

要操作文件需要将"项目名.entitlements"文件里配置的 App Sandbox 设置为 No

*/

/*

原理描述:

Resources资源同名文件在包更新时会被替换掉,所以为了保留用户本地已有数据,数据库需要采用不同文件名进行操作,具体如下

static let dbName = "db.db" // 原始数据库,应用的原始空数据库

static let dbLocalName = "dbLocal.db" // 用户本地数据库,就是db.db的复制品,只是记录了用户的数据

static let dbLocalTempName = "dbLocalTemp.db" // 更新数据库时用的临时数据库

// 数据库版本(数据库表中设置一个字段dbVersion,可用数字(比如1或字符串1.0.0)存储,记录当前数据库的版本。用于判断数据库是否已更新)

在版本升级的时候,为了保证用户已有数据,而且新数据库表结构字段可能增删改,不能随随便便替换数据库,而是要合理地"转移"数据。

(用数据库版本为标识是否已更新数据库)

数据库更新处理方式:

第1步,判断是否已更新过数据库(拿db.db和dbLocal.db中的dbVersion做比较,如果已更新过了就不用执行后续步骤)

第2步,原始数据库db.db->(直接复制一份)->临时数据库dbLocalTemp.db

第3步,本地数据库dbLocal.db->(用SQL查询语句将用户数据复制到)->临时数据库dbLocalTemp.db

【因为新的数据库可能增加了属性或新的表,所以要以新的数据库为中心,将数据填充完整后替换。而不是在旧的正式数据库做修改,这样很麻烦而且容易遗漏】

第4步,删除本地数据库dbLocal.db

第5步,临时数据库dbLocalTemp.db改名为dbLocal.db

数据库表的设计越复杂,具体复制数据的脚步就会越麻烦,所以最好是用服务器数据库存储数据,这样就算数据库改动也方便操作。本地数据库没事就别瞎改动了。

*/

Swift 复制代码
/*
 注,操作SQLite数据库采用第三方
 SQLite.swift
 https://gitter.im/stephencelis/SQLite.swift
 */


import AppKit
import SQLite

class DBControl {
    

    static func initializeDB() {
        /* 这里本人将数据库文件放到了项目的资源根目录下(与Info.plist同级) */
        let dbPath = filePath(inResourceDirectory: Config.dbName)
        let dbLocalPath = filePath(inResourceDirectory: Config.dbLocalName)
        let dbLocalTempPath = filePath(inResourceDirectory: Config.dbLocalTempName)
        
        if (!fileExists(inResourceDirectory: Config.dbLocalName)) {
            print("没有本地数据库")
            // 没有数据库,直接复制数据库过去
            do {
                try FileManager.default.copyItem(atPath: dbPath, toPath: dbLocalPath)
                print("复制成功")
            } catch {
                print("复制失败")
            }
        }
        else {
            // 有数据库
            print("有数据库");
            // 判断是否已更新
            var dbVersion:String = ""
            var dbLocalVersion:String = ""
            do {
                let dbCon = try Connection(dbPath)
                let dbLocalCon = try Connection(dbLocalPath)
                dbVersion = try dbCon.scalar("SELECT value FROM Records where 1=1 and name='DBVersion'") as! String // 根据自己创建的表结构进行查询
                dbLocalVersion = try dbLocalCon.scalar("SELECT value FROM Records where 1=1 and name='DBVersion'") as! String
            } catch {
            }
            
            print("dbLocalVersion=\(dbLocalVersion)")
            print("dbVersion=\(dbVersion)")
            
            if (Int(dbLocalVersion)  ?? 0 >= Int(dbVersion) ?? 0) {
                // 已更新过了,无需再更新
                print("已更新过了,无需再更新");
            }
            else {
                // 需要更新
                print("需要更新");
                // 复制临时数据库
                do {
                    if (fileExists(inResourceDirectory: Config.dbLocalTempName)) {
                        try FileManager.default.removeItem(at: URL(fileURLWithPath: dbLocalTempPath)) // 如果有先删掉
                    }
                    try FileManager.default.copyItem(atPath: dbPath, toPath: dbLocalTempPath)
                    print("临时数据库复制成功")
                } catch {
                }
                
                // 更新数据(!!!根据表的变动情况,灵活采用更新的方式)
                do {
                    // 1、复制数据
                    // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓这里有一大堆代码要写↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
                    // 将dbLocal.db内表的数据复制到dbLocalTemp.db
                    // 就是写一大堆SQL语句执行
                    // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑这里有一大堆代码要写↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                    
                    // 2、删除本地数据库dbLocal.db
                    try FileManager.default.removeItem(at: URL(fileURLWithPath: dbLocalPath))

                    // 3、临时数据库dbLocalTemp.db改名为dbLocal.db
                    try FileManager.default.moveItem(at: URL(fileURLWithPath: dbLocalTempPath), to: URL(fileURLWithPath: dbLocalPath))
                } catch {
                }
            }
        }
    }
    
    static func filePath(inResourceDirectory resourceName: String) -> String {
        /*
         注意!!!
         1、直接用Bundle.main.path(forResource: "", ofType: "")来获取路径的话,如果文件不存在则返回的结果为nil,不是想要的路径字符串
         2、刚刚新动态创建的文件,明明确确实实已经存在于那里了,立马用Bundle.main.path(forResource: "", ofType: "")去获取路径,得到的结果却是nil,甚是离谱
         */
        //
        
        // 获取应用程序的bundle
        if let bundlePath = Bundle.main.resourceURL?.path {
            // 构建资源目录的完整路径
//            let resourceDirectoryPath = URL(fileURLWithPath: bundlePath).appendingPathComponent("Resources").path
            let resourceDirectoryPath = URL(fileURLWithPath: bundlePath).appendingPathComponent("").path // (以根目录为基)其下具体哪个文件夹路径,这里不写,写在参数resourceName里
            // 构建要检查的文件路径
            let filePath = URL(fileURLWithPath: resourceDirectoryPath).appendingPathComponent(resourceName).path
            return filePath
        }
        return ""
    }
    
    static func fileExists(inResourceDirectory resourceName: String) -> Bool {
        // 获取应用程序的bundle
        if let bundlePath = Bundle.main.resourceURL?.path {
            // 构建资源目录的完整路径
//            let resourceDirectoryPath = URL(fileURLWithPath: bundlePath).appendingPathComponent("Resources").path
            let resourceDirectoryPath = URL(fileURLWithPath: bundlePath).appendingPathComponent("").path //  (以根目录为基)其下具体哪个文件夹路径,这里不写,写在参数resourceName里
            // 构建要检查的文件路径
            let filePath = URL(fileURLWithPath: resourceDirectoryPath).appendingPathComponent(resourceName).path
            // 使用FileManager检查文件是否存在
            let fileManager = FileManager.default
            var isDirectory: ObjCBool = false
            let fileExists = fileManager.fileExists(atPath: filePath, isDirectory: &isDirectory)
//            print("filePath=\(filePath)")
            // 返回文件是否存在
            return fileExists && !isDirectory.boolValue // 确保不是目录
        }
        return false
    }
}
相关推荐
胚芽鞘68124 分钟前
关于java项目中maven的理解
java·数据库·maven
sun0077004 小时前
mysql索引底层原理
数据库·mysql
2501_915918414 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview
不知名It水手5 小时前
uniapp运行项目到ios基座
ios·uni-app·cocoa
Digitally6 小时前
[5种方法] 如何将iPhone短信保存到电脑
ios·iphone
workflower6 小时前
MDSE和敏捷开发相互矛盾之处:方法论本质的冲突
数据库·软件工程·敏捷流程·极限编程
Tony小周7 小时前
实现一个点击输入框可以弹出的数字软键盘控件 qt 5.12
开发语言·数据库·qt
lifallen7 小时前
Paimon 原子提交实现
java·大数据·数据结构·数据库·后端·算法
TDengine (老段)7 小时前
TDengine 数据库建模最佳实践
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
Elastic 中国社区官方博客7 小时前
Elasticsearch 字符串包含子字符串:高级查询技巧
大数据·数据库·elasticsearch·搜索引擎·全文检索·lucene