如何将 Room 数据库导出为 .db 以下载文件以便稍后使用?

2024-03-19

如何将 Room 数据库导出到 .db 文件以便稍后使用? 我想将其导出到 Android 设备存储中的下载文件夹。我已经设置导出到 .CSV,但现在我需要导出到 .db。

这样,如果用户正在切换设备并且需要其他设备上的数据,我可以重新导入它。

有没有办法只使用 .CSV 数据,还是必须使用 .db?


备份数据库文件要简单得多,而且可能比尝试操作 CSV 数据更快。

还有一个优点是只需复制文件即可在 SQLite Tools 中使用数据库。

附:扩展名(例如 .csv .db)仅表明文件的用途。在示例中.whatever已经用过。

  • 一个问题是,恢复数据库后,访问恢复的数据可能会出现问题。

    • 避免此类问题的一种方法是在恢复后自动重新启动应用程序。
  • 另一个问题是 Room 默认使用预写日志记录 (WAL),并且检查数据库似乎并不那么容易。

    • 作为解决方案,可以备份和恢复 3 个文件(wal 文件和 shm 文件),而不是备份单个文件,请参阅https://www.sqlite.org/wal.html https://www.sqlite.org/wal.html

这是一个示例/演示应用程序,它使用 Room 数据库和一个只有 2 列的表。当应用程序运行时,有一个具有 4 个 UI 组件的 Activity:-

  • 允许备份数据库的按钮。

  • 允许恢复数据库的按钮。

  • 允许添加一些数据的按钮(3 行)。

  • 当前数据的列表。

  • 请注意,为了简单起见,所有工作都在主线程上进行

  • 另外,为了简单起见,所有数据库代码都位于一个文件中房间实体

  • 另外为了简单起见,备份到同一目录data/data/the_package_name/databases

房间实体(所有数据库代码,包括备份和恢复代码):-

const val THETABLE_TABLENAME = "theTable"
const val THETABLE_ID_COLUMN = THETABLE_TABLENAME + "_id"
const val TheTABLE_OTHER_COLUMN = THETABLE_TABLENAME + "_other"
const val THEDATABASE_DATABASE_NAME = "thedatabase.whatever"
const val THEDATABASE_DATABASE_BACKUP_SUFFIX = "-bkp"
const val SQLITE_WALFILE_SUFFIX = "-wal"
const val SQLITE_SHMFILE_SUFFIX = "-shm"
@Entity(tableName = THETABLE_TABLENAME)
data class TheTable(
    @PrimaryKey @ColumnInfo(name = THETABLE_ID_COLUMN) var id: Long?=null,
    @ColumnInfo(name = TheTABLE_OTHER_COLUMN) var other: String
)
@Dao
interface AllDAO {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(theTable: TheTable): Long
    @Query("SELECT * FROM $THETABLE_TABLENAME")
    fun getAllFromTheTable(): List<TheTable>
}
@Database(entities = [TheTable::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDAO

    companion object {
        private var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,
                    THEDATABASE_DATABASE_NAME)
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }

    /**
     * Backup the database
     */
    fun backupDatabase(context: Context): Int {
        var result = -99
        if (instance==null) return result

        val dbFile = context.getDatabasePath(THEDATABASE_DATABASE_NAME)
        val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
        val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
        val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
        val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
        val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
        if (bkpFile.exists()) bkpFile.delete()
        if (bkpWalFile.exists()) bkpWalFile.delete()
        if (bkpShmFile.exists()) bkpShmFile.delete()
        checkpoint()
        try {
            dbFile.copyTo(bkpFile,true)
            if (dbWalFile.exists()) dbWalFile.copyTo(bkpWalFile,true)
            if (dbShmFile.exists()) dbShmFile.copyTo(bkpShmFile, true)
            result = 0
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return result
    }

    /**
     *  Restore the database and then restart the App
     */
    fun restoreDatabase(context: Context,restart: Boolean = true) {
        if(!File(context.getDatabasePath(THEDATABASE_DATABASE_NAME).path + THEDATABASE_DATABASE_BACKUP_SUFFIX).exists()) {
            return
        }
        if (instance == null) return
        val dbpath = instance!!.getOpenHelper().readableDatabase.path
        val dbFile = File(dbpath)
        val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
        val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
        val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
        val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
        val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
        try {
            bkpFile.copyTo(dbFile, true)
            if (bkpWalFile.exists()) bkpWalFile.copyTo(dbWalFile, true)
            if (bkpShmFile.exists()) bkpShmFile.copyTo(dbShmFile,true)
            checkpoint()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        if (restart) {
            val i = context.packageManager.getLaunchIntentForPackage(context.packageName)
            i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            context.startActivity(i)
            System.exit(0)
        }
    }
    private fun checkpoint() {
        var db = this.getOpenHelper().writableDatabase
        db.query("PRAGMA wal_checkpoint(FULL);",null)
        db.query("PRAGMA wal_checkpoint(TRUNCATE);",null)
    }
}
  • 请注意,检查点函数似乎并未实际检查点(关闭数据库,这将对数据库进行检查点,导致 Room 框架/包装器出现问题)

从代码中可以看出,备份和恢复基本上只是复制文件。

活动的布局是:-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        />
    <Button
        android:id="@+id/backup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BACKUP"
        >
    </Button>
    <Button
        android:id="@+id/restore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RESTORE"
        >
    </Button>
    <Button
        android:id="@+id/addData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ADD DATA"
        >
    </Button>
    <ListView
        android:id="@+id/datalist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >
    </ListView>
</LinearLayout>

活动(主要活动)是:-

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDAO
    lateinit var backupButton: Button
    lateinit var restoreButton: Button
    lateinit var addDataButton: Button
    lateinit var listView: ListView
    var listAdapter: SimpleCursorAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        backupButton = this.findViewById(R.id.backup)
        restoreButton = this.findViewById(R.id.restore)
        addDataButton = this.findViewById(R.id.addData)
        listView = this.findViewById(R.id.datalist)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()
        setButtonListeners()
        setOrRefreshListView()
    }

    fun setButtonListeners() {
        addDataButton.setOnClickListener {
            dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
            dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
            dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
            setOrRefreshListView()
        }
        backupButton.setOnClickListener {
            db.backupDatabase(this)
        }
        restoreButton.setOnClickListener {
            db.restoreDatabase(this)
        }

    }

    fun setOrRefreshListView() {
        var csr = db.getOpenHelper().writableDatabase.query("SELECT $THETABLE_ID_COLUMN AS _id, $TheTABLE_OTHER_COLUMN FROM $THETABLE_TABLENAME")
        if (listAdapter==null) {
            listAdapter = SimpleCursorAdapter(
                this,
                android.R.layout.simple_list_item_2,
                csr,
                arrayOf("_id", TheTABLE_OTHER_COLUMN),
                intArrayOf(android.R.id.text1,android.R.id.text2), 0
            )
            listView.adapter = listAdapter
        } else {
            listAdapter!!.swapCursor(csr)
        }
    }
}

Results

第一次运行时,显示为:-

  • Ideally the Restore button should not appear as there is, at this stage nothing to restore from
    • 单击“恢复”按钮即可,因为恢复功能会检查是否有任何恢复文件,如果没有则立即返回

如果单击备​​份按钮,则会备份文件(即使没有数据(但文件中实际上会有一些数据,例如文件头和系统表))。

如果单击“备份”按钮后,单击“恢复”按钮(即使没有添加数据),则数据库实际上已恢复,并且应用程序将重新启动。

如果单击“添加数据”按钮,则会添加并显示 3 行数据(如果再次单击,则会添加另外 3 行数据,依此类推)。

由于先前单击了“备份”按钮。点击恢复按钮将恢复到没有数据的备份,并重新启动App,显示无数据。

添加2组数据并点击备份按钮

添加另外2组数据

点击恢复则数据恢复为仅6行数据

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何将 Room 数据库导出为 .db 以下载文件以便稍后使用? 的相关文章

随机推荐

  • 实体框架 4.0 GetChanges() 等效项

    在 LINQ to SQL 中 您可以重写 SubmitChanges 并使用 this GetChangeSet 方法来获取所有插入 更新和删除 以便您可以在将更改提交到数据库之前进行最后一刻的更改 这可以在 EF 4 0 中完成吗 我看
  • RethinkDB:​​RqlRuntimeError:无法对序列序列执行括号

    给定表格中的以下文档 id d30aa369 99d6 4a40 9547 2cbdf0bb069a locations alerts person 200 person 300 name home alerts person 200 pe
  • 不会更新目标组件,但 工作正常[重复]

    这个问题在这里已经有答案了 我在让 Ajax 正常工作时遇到问题 在我这里的 xhtml 文件中 我使用 Ajax 来呈现一些启用或禁用的输入 并且它工作正常 然而 更进一步 我还使用 Ajax 来渲染包含的 xhtml 文件 其中包含其他
  • 处理 GUI 中未处理的异常

    我主要是为精通技术的人编写一个小工具 例如由于这些工具通常是随着时间的推移而改进的快速黑客 我知道将会出现未处理的异常 并且用户不会介意 我希望用户能够向我发送回溯 以便我可以检查发生的情况并可能改进应用程序 我通常做 wxPython 编
  • Activiti 6.0.0 完成任务时无法获取表单属性

    我是 Activiti 6 0 0 的新手 我创建了一个包含用户任务的进程 第二个用户任务有两个表单属性 但是当我完成第一个用户任务并尝试完成第二个用户任务时 表单属性不显示 我无法完成用户任务 下面是我的 bpm 流程
  • 如何获取生成BIGINT类型sql的原则?

    在我的架构中 我有许多需要的字段BIGINT 我使用 Symfony 中的以下命令 symfony doctrine build sql 生成我的数据库 字段总是以类型的形式出现int 我在架构中尝试了以下类型 int type integ
  • 防止CSRF?

    我已经从这里看到了一些问题 stackoverflow 并且THIS http www codinghorror com blog 2008 10 preventing csrf and xsrf attacks html发帖了 但我还有一
  • 使用字符串分区键与整数分区键的 Hive/Impala 性能

    是否建议将数字列用作分区键 当我们对数字列分区和字符串列分区进行选择查询时 性能会有什么差异吗 好吧 如果你查阅 Impala 官方文档 就会有所不同 我不会详细说明 而是粘贴文档中的部分 因为我认为它说得很好 虽然使用 STRING 列作
  • 更新到 Angular 7 后出错。类型为“string | 的参数” ArrayBuffer' 不可分配给'string' 类型的参数

    我将我的项目从 Angular 6 升级到 Angular 7 我的项目中有一个文件上传组件 升级后会出现编译器错误 onUpload const fileReader new FileReader fileReader onload gt
  • is_account_page() 的 WooCommerce 条件,但仅限登录部分

    我需要测试用户是否在帐户页面上 但仅限于标题所述的登录部分 有没有办法做到这一点 可能你需要结合 is user logged in with is account page 这边走 if is user logged in is acco
  • 在哪里定义 topic.metadata.refresh.interval.ms?

    我正在对 kafka 进行一些测试 希望很快就能将其放入我的生产堆栈中 我正在使用这些文件kafka console producer sh and kafka console consumer sh测试kafka的功能 我创建了一个包含
  • 编写日志传送自动化脚本

    是否可以编写所有日志传送配置的脚本 例如 我需要编写一个作业 启动从服务器 A 到服务器 B 的两个数据库的日志传送 该作业将在服务器 A 上运行 顺便说一句 两者都是 SQL 2008 R2 Enterprise 服务器A上有一个共享 服
  • PyCharm:版本控制 .idea 文件夹,同时在开发人员之间保留不同的解释器

    我们使用 PyCharm 作为项目的 Python IDE 开发人员使用不同类型的操作系统设置 例如 python 路径对于我们中的某些人来说是不一样的 有些人将本地解释器存储在不同的位置或远程解释器 不幸的是 python解释器路径存储在
  • Scala:输入流到数组[字节]

    使用 Scala 从 InputStream 读取字节数组的最佳方法是什么 我可以看到您可以将 InputStream 转换为 char 数组 Source fromInputStream is toArray 怎么样 Stream con
  • 在 Clojure 中使用 WSDL

    我需要使用 WSDL Web 服务 而到目前为止我看到的 Java 客户端代码看起来臃肿且复杂 我想知道 Clojure 中是否存在更清晰的解决方案 以便我可以在 Clojure 中实现该部分并向 Java 代码公开更简单的 API cd
  • Firebase createCustomToken() 在本地模拟器中返回无效令牌

    我正在使用 firebase 身份验证来处理我的 Web 应用程序的用户帐户 当用户输入有效的登录凭据时 我使用 firebase admin SDK 在云函数中生成 JWT 该 JWT 被发送回客户端 然后使用 firebase 进行身份
  • matplotlib.rc 和 matplotlib.pyplot.rc 有什么区别?

    据我所知 在 matplotlib 中 您可以使用 rc 或 rcParams 来自定义绘图的样式 但是 这些函数似乎存在于两个级别 例如 matplotlib rc 与 matplotlib pyplot rc 或 matplotlib
  • 在 jQuery 中获取鼠标滚轮事件?

    有没有办法获取鼠标滚轮事件 不讲scroll事件 在 jQuery 中 document ready function foo bind mousewheel function e if e originalEvent wheelDelta
  • 如何使用QMake的子目录模板?

    我开始学习Qt 我即将离开 Visual Studio 世界 正在寻找一种使用 QMake 组织项目结构的方法 我找到了 子目录 模板 但我很难理解它 我的项目结构如下所示 project dir main cpp project pro
  • 如何将 Room 数据库导出为 .db 以下载文件以便稍后使用?

    如何将 Room 数据库导出到 db 文件以便稍后使用 我想将其导出到 Android 设备存储中的下载文件夹 我已经设置导出到 CSV 但现在我需要导出到 db 这样 如果用户正在切换设备并且需要其他设备上的数据 我可以重新导入它 有没有