是否可以使用 sqldelight 从本地资源加载预填充的数据库

Posted

技术标签:

【中文标题】是否可以使用 sqldelight 从本地资源加载预填充的数据库【英文标题】:Is it possible to load a pre-populated database from local resource using sqldelight 【发布时间】:2019-12-12 04:07:38 【问题描述】:

我有一个相对较大的数据库,可能需要 1 到 2 分钟来初始化,是否可以在使用 sqldelight(kotlin 多平台)而不是在应用启动时初始化数据库时加载预填充的数据库?

【问题讨论】:

【参考方案1】:

是的,但这可能很棘手。不仅仅是“多平台”。在尝试初始化 sqldelight 之前,您需要将 db 复制到 db 文件夹。这可能意味着应用启动时在主线程上进行 i/o。

现在没有标准的方法来做到这一点。在初始化 sqldelight 之前,您需要将 db 文件放在 android 上的 assets 和 ios 上的 bundle 中,并将它们复制到各自的文件夹中。显然,您需要先检查数据库是否存在,或者通过某种方式知道这是您第一次运行应用程序。

如果您计划发布具有较新数据库的更新,则需要管理版本,而不仅仅是检查数据库是否存在。

虽然没有直接回答你的问题,但 1 到 2 分钟对于 sqlite 来说真的非常非常长。你在干嘛?我首先要确保您正确使用交易。插入数据 1-2 分钟(可能)会产生一个巨大的 db 文件。

【讨论】:

我正在尝试 kotlin 多平台,并且我有一个搜索应用程序,其中包含本地数据库上的一本书以及所有唯一单词 + 单词映射到句子和页面,这对于 sqlight 来说意义重大。我有点在 io 协程上执行 db init,但希望完全避免 db init(很容易:))。下一个最好的事情是能够随意直接在数据库上运行 .sq 文件的内容,即与表 init .sq 文件分开,这可行吗?它允许我使用基本信息初始化数据库,并在后台线程上运行其余的初始化作业。 我不知道您是否可以运行任意 .sq 文件,但如果您直接获得 sqlite 驱动程序的句柄,您可以在资源文件中包含 sql 语句并运行它们。如果我了解您的用例,您有一张表,其中包含大约 2000-5000 个插入(单词列表)和一个包含 50k-100k 插入(地图)的表,使用快速网络搜索小说大小的统计数据。我会开始尝试简单地提高插入性能 (medium.com/@JasonWyatt/…),但假设您在事务中有插入,您可能无法加快速度 我会尝试降低映射表的复杂性,看看您是否可以加快速度,因为这将是您的大部分时间。否则,只需有一个特殊的初始化流程,在应用程序第一次运行时将 db 文件从资源复制到 db 文件夹。显示进度条。那种事。拥有一个部分初始化的数据库似乎很复杂。 是的,你理解正确。必须从 android 和 ios 上的资源加载 db 感觉需要做很多工作 + 这意味着共享项目不会是唯一初始化数据的地方。我最终做的是,将所有少量插入留在 sqldelight .sq 表文件中,并将大插入(在本例中为映射)移动到初始化进程。首次运行应用程序时,图书内容将可供浏览,但在数据库初始化完成之前搜索功能将不可用,会显示一个进度条来指示这一点。 在我的初始化过程中,我将 sql 插入转换为存储在列表中的 kotlin 对象,分成多个文件。在第一次运行时,这些映射对象是使用我在映射表 .sq 文件中定义的 sqldelight 插入插入的。正如您所提到的,如果我可以获得 sqlite 驱动程序的句柄,我可以只运行 sql 语句而无需转换它们,但不确定这是否会显着改善插入所有映射所花费的时间。【参考方案2】:

抱歉,我还不能添加任何 cmets,这样会更合适...


虽然没有直接回答你的问题,但1到2分钟是 真的,真的很渴望 sqlite。你在干嘛?我会先做 确保您正确使用事务。插入数据 1-2 分钟 会(可能)导致一个巨大的数据库文件。

另外,我不得不使用预填充数据库的问题与 .sq 文件的大尺寸(每个表超过 30 MB 的 INSERT 文本)有关,并且 SqlDeLight 静默中断了生成,没有显示错误消息。


您需要将 db 文件放在 android 上的 assets 中,然后放在 bundle 中 iOS 并在初始化之前将它们复制到各自的文件夹中 sqldelight。

不得不从 android 和 ios 上的资源中加载一个 db 感觉很多 工作+这意味着共享项目不会是唯一的地方 数据已初始化。

Kotlin MultiPlatform 库Moko-resources 解决了共享模块中数据库的单一来源问题。它适用于 KMM 的方式与适用于 Android 和 iOS 的方式相同。

不幸的是,图书馆的样本中几乎没有使用此功能。我在预期的类 DatabaseDriverFactory 中添加了第二个方法(getDriver)来打开准备好的数据库,并在平台上实现它。例如,对于 androidMain:

actual class DatabaseDriverFactory(private val context: Context) 
    actual fun createDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver 
        return AndroidSqliteDriver(schema, context, fileName)
    

    actual fun getDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver 
        val database: File = context.getDatabasePath(fileName)

        if (!database.exists()) 
            val inputStream = context.resources.openRawResource(MR.files.dbfile.rawResId)
            val outputStream = FileOutputStream(database.absolutePath)

            inputStream.use  input: InputStream ->
                outputStream.use  output: FileOutputStream ->
                    input.copyTo(output)
                
            
        

        return AndroidSqliteDriver(schema, context, fileName)
    

MR.files.fullDb 是库生成的类中的 FileResource,它与位于 commonMain 模块的 resources/MR/files 目录中的文件名相关联。它的属性rawResId代表平台端资源ID。

【讨论】:

【参考方案3】:

您唯一需要的是使用驱动程序指定数据库文件的路径。

假设您的数据库位于/mnt/my_best_app_dbs/super.db。现在,在驱动程序的name 属性中传递路径。像这样的:

val sqlDriver: SqlDriver = AndroidSqliteDriver(Schema, context, "/mnt/my_best_app_dbs/best.db")

请记住,您可能需要具有允许您读取给定存储类型的权限。

【讨论】:

我认为这不是真的。您需要将数据库显式复制到默认文件夹。 @nayriz 试试看,它在我的项目中运行良好? 我做到了,并且可以确认它对我不起作用。在 Kotlin 的 slack 中,讨论了如何在 Room 中实现此功能,而不是使用 SQL 来实现。 @Denis Luttcev 解决方案是最好的 AFAIK。 您的应用是否已授予存储权限?上面的行是从它工作的项目中复制的,但它是一个系统应用程序,所有权限都是开箱即用的。

以上是关于是否可以使用 sqldelight 从本地资源加载预填充的数据库的主要内容,如果未能解决你的问题,请参考以下文章

我可以在同一个项目中将 sqldelight 与不同的数据库一起使用吗?

Http缓存

从资源包(非主)加载本地化的 xib 文件

在 uiwebview 中使用本地 html 从相对路径加载资源

浏览器缓存

WKWebView 确实从本地文档文件夹加载资源