备份室数据库
Backup Room database
我正在尝试以编程方式备份房间数据库。
为此,我只是复制包含整个数据库的 .sqlite
文件
但是,在复制之前,由于房间启用了 write ahead logging,我们必须关闭数据库,以便 -shm
文件和 -wal
文件合并为一个 .sqlite
文件。
我 运行 .close()
在 RoomDatabase
对象上:
备份一切正常,但是,稍后,当我尝试执行 INSERT
查询时,出现此错误:
android.database.sqlite.SQLiteException: no such table: room_table_modification_log (code 1)
关闭room db后如何正确重新打开?
PS: .isOpen()
在 RoomDatabase
对象 returns true
之前 INSERT
房间版本:1.1.1-rc1
How can I properly re-open room db after I close it?
很抱歉这没有回答那个问题。
但如果将所有内容移动到原始数据库文件是您想要做的,那么您不必首先关闭数据库。您可以改为使用 wal_checkpoint
pragma.
强制检查点
对数据库查询以下语句。我们在这里使用原始查询,因为 Room 尚不支持 pragma
(它将触发 UNKNOWN query type
错误)。在你的 DAO 中有这个查询:
@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
然后当你调用checkpoint方法时,使用query then:
myDAO.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
This link 可能会阐明 wal_checkpoint
的作用。
为了更具体地回答您的问题,这就是我在我的一个应用程序中备份房间数据库的方式。
- 检查读取/写入外部存储的权限。如果写入App files目录可以忽略此步骤
- 关闭你的
RoomDatabase
。在我的例子中,AppDatabase
指的是一个包含最初构建房间数据库的逻辑的单例。 AppDatabase.getInstance(this).getDatabase()
获取单例的当前实例及其从 RoomDatabase
扩展的当前数据库 class。这实际上调用了 RoomDatabase.close()
.
- 根据备份或还原定义源文件和目标文件。我包括 shm 和 wal 文件,即使它们是临时文件。
- 使用您选择的方法复制文件。
FileUtils
在这种情况下,指的是 commons-io
.
代码
if(id == R.id.action_save_db) {
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permission == PackageManager.PERMISSION_GRANTED) {
AppDatabase.getInstance(this).getDatabase().close();
File db = getDatabasePath("my-db");
File dbShm = new File(db.getParent(), "my-db-shm");
File dbWal = new File(db.getParent(), "my-db-wal");
File db2 = new File("/sdcard/", "my-db");
File dbShm2 = new File(db2.getParent(), "my-db-shm");
File dbWal2 = new File(db2.getParent(), "my-db-wal");
try {
FileUtils.copyFile(db, db2);
FileUtils.copyFile(dbShm, dbShm2);
FileUtils.copyFile(dbWal, dbWal2);
} catch (Exception e) {
Log.e("SAVEDB", e.toString());
}
} else {
Snackbar.make(mDrawer, "Please allow access to your storage", Snackbar.LENGTH_LONG)
.setAction("Allow", view -> ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 0)).show();
}
} else if(id == R.id.action_load_db) {
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if(permission == PackageManager.PERMISSION_GRANTED) {
AppDatabase.getInstance(this).getDatabase().close();
File db = new File("/sdcard/", "my-db");
File dbShm = new File(db.getParent(), "my-db-shm");
File dbWal = new File(db.getParent(), "my-db-wal");
File db2 = getDatabasePath("my-db");
File dbShm2 = new File(db2.getParent(), "my-db-shm");
File dbWal2 = new File(db2.getParent(), "my-db-wal");
try {
FileUtils.copyFile(db, db2);
FileUtils.copyFile(dbShm, dbShm2);
FileUtils.copyFile(dbWal, dbWal2);
} catch (Exception e) {
Loge("RESTOREDB", e.toString());
}
} else {
Snackbar.make(mDrawer, "Please allow access to your storage", Snackbar.LENGTH_LONG)
.setAction("Allow", view -> ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.READ_EXTERNAL_STORAGE
}, 0)).show();
}
}
作为替代方案,您始终可以创建 Room 数据库,同时强制它不使用预写日志记录:
Room.databaseBuilder(context, db.class, dbName)
.setJournalMode(JournalMode.TRUNCATE)
.build();
首先,必须关闭数据库才能应用 "dbName.db-wal"
文件中的更改。
然后您可以复制包含所有表和最后数据更改的数据库
AppDatabase appDatabase = AppDatabase.getAppDatabase(getApplicationContext());
appDatabase.close();
上面已经回答过了。不需要close/re-open数据库。
我在我的 android 应用程序中使用 MVVM 模式来备份数据库文件以将其上传到 google 驱动器。只想总结对我有用的解决方案:
在您的 DAO 文件中提及以下代码:
@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
在您的存储库文件中提及以下代码:
/* Android database has three files under /data/data/com.package.app/databases/
** test.db, test.db-shm, test.db-wal - those extra files have recent commits.
** To merge data from other shm and wal files to db, run following method - useful before taking backup.
*/
void checkPoint() {
ItemRoomDatabase.databaseWriteExecutor.execute(() -> {
itemDao.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
});
}
在您的 ViewModel 中提及以下内容:
public void checkPoint() {
itemRepository.checkPoint();
}
现在您可以在从 Activity 文件进行备份之前调用此方法
ItemViewModel itemViewModel = new ViewModelProvider(this).get(ItemViewModel.class);
itemViewModel.checkPoint();
我正在使用这个库来备份和重新存储房间数据库,它非常容易使用。
https://github.com/salehyarahmadi/RoomDatabaseBackupAndRestore
感谢 salehyarahmadi
首先需要做的是创建具有适当日志模式的数据库。
Room.databaseBuilder(context, AppDatabase::class.java, name)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.build()
之后需要执行以下检查点查询以确保应用所有未决事务。
为此,需要将以下方法添加到数据库 Dao 接口
interface UserDao {
@RawQuery
fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery?): Single<Int>
}
然后需要使用以下SQL查询来调用该方法
userDao.checkpoint((SimpleSQLiteQuery("pragma wal_checkpoint(full)")))
检查点方法成功后,数据库备份文件终于可以保存了。
最后可以使用以下代码检索数据库备份文件
File(database.openHelper.writableDatabase.path)
然后需要将该文件复制到备份文件位置。
要恢复文件,唯一需要做的就是用备份文件覆盖数据库文件(可以使用上面的代码片段检索)。
您可以在我的博客上阅读更多详细信息
https://androidexplained.github.io/android/room/2020/10/03/room-backup-restore.html
为了更具体地回答您的问题,这就是我在我的一个应用程序中备份房间数据库的方式。
1-检查读取/写入外部存储的权限。
2-关闭您的 RoomDatabase。在我的例子中,AppDatabase 指的是一个包含最初构建房间数据库的逻辑的单例。 AppDatabase.getInstance(this@MainActivity) 获取单例的当前实例及其从 RoomDatabase 扩展的当前数据库 class。
3-然后基本上调用 dbInstance.close().
private fun createBackup() {
val db = AppDatabase.getInstance(this@MainActivity)
db.close()
val dbFile: File = getDatabasePath(DATABASE_NAME)
val sDir = File(Environment.getExternalStorageDirectory(), "Backup")
val fileName = "Backup (${getDateTimeFromMillis(System.currentTimeMillis(), "dd-MM-yyyy-hh:mm")})"
val sfPath = sDir.path + File.separator + fileName
if (!sDir.exists()) {
sDir.mkdirs()
}
val saveFile = File(sfPath)
if (saveFile.exists()) {
Log.d("LOGGER ", "File exists. Deleting it and then creating new file.")
saveFile.delete()
}
try {
if (saveFile.createNewFile()) {
val bufferSize = 8 * 1024
val buffer = ByteArray(bufferSize)
var bytesRead: Int
val saveDb: OutputStream = FileOutputStream(sfPath)
val indDb: InputStream = FileInputStream(dbFile)
do {
bytesRead = indDb.read(buffer, 0, bufferSize)
if (bytesRead < 0)
break
saveDb.write(buffer, 0, bytesRead)
} while (true)
saveDb.flush()
indDb.close()
saveDb.close()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
您必须在
中包含保存文件
try {
//backup process
}
} catch (e: Exception) {
e.printStackTrace()
}
按照错误发生的顺序排列并避免应用程序崩溃。
并从 currentTimeMillis 获取日期
使用这个功能
fun getDateTimeFromMillis(millis: Long, pattern: String): String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.getDefault()).format(Date())
return simpleDateFormat.format(millis)
}
Resoting Db 的代码
正在将文件对象传递给 Uri.fromFile
try {
val fileUri: Uri = Uri.fromFile(file)
val inputStream = contentResolver.openInputStream(fileUri)
println("restoring ")
restoreDatabase(inputStream);
inputStream?.close()
} catch (e: IOException) {
println( e.message)
e.printStackTrace()
}
** 或以 activity 开头的结果返回结果 **
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 12 && resultCode == RESULT_OK && data != null) {
Uri fileUri = data.getData();
try {
assert fileUri != null;
InputStream inputStream = getContentResolver().openInputStream(fileUri);
restoreDatabase(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
restoreDatabase 函数
private fun restoreDatabase(inputStreamNewDB: InputStream?) {
val db = AppDatabase.getInstance(this@MainActivity)
db.close()
val oldDB = getDatabasePath(DATABASE_NAME)
if (inputStreamNewDB != null) {
try {
copyFile(inputStreamNewDB as FileInputStream?, FileOutputStream(oldDB))
println("restore success")
} catch (e: IOException) {
Log.d("BindingContextFactory ", "ex for is of restore: $e")
e.printStackTrace()
}
} else {
Log.d("BindingContextFactory ", "Restore - file does not exists")
}
}
现在您需要将文件从备份复制到真实的数据库文件中
使用复制文件
@Throws(IOException::class)
fun copyFile(fromFile: FileInputStream?, toFile: FileOutputStream) {
var fromChannel: FileChannel? = null
var toChannel: FileChannel? = null
try {
fromChannel = fromFile?.channel
toChannel = toFile.channel
fromChannel?.transferTo(0, fromChannel.size(), toChannel)
} finally {
try {
fromChannel?.close()
} finally {
toChannel?.close()
}
}
}
我正在尝试以编程方式备份房间数据库。
为此,我只是复制包含整个数据库的 .sqlite
文件
但是,在复制之前,由于房间启用了 write ahead logging,我们必须关闭数据库,以便 -shm
文件和 -wal
文件合并为一个 .sqlite
文件。
我 运行 .close()
在 RoomDatabase
对象上:
备份一切正常,但是,稍后,当我尝试执行 INSERT
查询时,出现此错误:
android.database.sqlite.SQLiteException: no such table: room_table_modification_log (code 1)
关闭room db后如何正确重新打开?
PS: .isOpen()
在 RoomDatabase
对象 returns true
之前 INSERT
房间版本:1.1.1-rc1
How can I properly re-open room db after I close it?
很抱歉这没有回答那个问题。
但如果将所有内容移动到原始数据库文件是您想要做的,那么您不必首先关闭数据库。您可以改为使用 wal_checkpoint
pragma.
对数据库查询以下语句。我们在这里使用原始查询,因为 Room 尚不支持 pragma
(它将触发 UNKNOWN query type
错误)。在你的 DAO 中有这个查询:
@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
然后当你调用checkpoint方法时,使用query then:
myDAO.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
This link 可能会阐明 wal_checkpoint
的作用。
为了更具体地回答您的问题,这就是我在我的一个应用程序中备份房间数据库的方式。
- 检查读取/写入外部存储的权限。如果写入App files目录可以忽略此步骤
- 关闭你的
RoomDatabase
。在我的例子中,AppDatabase
指的是一个包含最初构建房间数据库的逻辑的单例。AppDatabase.getInstance(this).getDatabase()
获取单例的当前实例及其从RoomDatabase
扩展的当前数据库 class。这实际上调用了RoomDatabase.close()
. - 根据备份或还原定义源文件和目标文件。我包括 shm 和 wal 文件,即使它们是临时文件。
- 使用您选择的方法复制文件。
FileUtils
在这种情况下,指的是commons-io
.
代码
if(id == R.id.action_save_db) {
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permission == PackageManager.PERMISSION_GRANTED) {
AppDatabase.getInstance(this).getDatabase().close();
File db = getDatabasePath("my-db");
File dbShm = new File(db.getParent(), "my-db-shm");
File dbWal = new File(db.getParent(), "my-db-wal");
File db2 = new File("/sdcard/", "my-db");
File dbShm2 = new File(db2.getParent(), "my-db-shm");
File dbWal2 = new File(db2.getParent(), "my-db-wal");
try {
FileUtils.copyFile(db, db2);
FileUtils.copyFile(dbShm, dbShm2);
FileUtils.copyFile(dbWal, dbWal2);
} catch (Exception e) {
Log.e("SAVEDB", e.toString());
}
} else {
Snackbar.make(mDrawer, "Please allow access to your storage", Snackbar.LENGTH_LONG)
.setAction("Allow", view -> ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 0)).show();
}
} else if(id == R.id.action_load_db) {
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if(permission == PackageManager.PERMISSION_GRANTED) {
AppDatabase.getInstance(this).getDatabase().close();
File db = new File("/sdcard/", "my-db");
File dbShm = new File(db.getParent(), "my-db-shm");
File dbWal = new File(db.getParent(), "my-db-wal");
File db2 = getDatabasePath("my-db");
File dbShm2 = new File(db2.getParent(), "my-db-shm");
File dbWal2 = new File(db2.getParent(), "my-db-wal");
try {
FileUtils.copyFile(db, db2);
FileUtils.copyFile(dbShm, dbShm2);
FileUtils.copyFile(dbWal, dbWal2);
} catch (Exception e) {
Loge("RESTOREDB", e.toString());
}
} else {
Snackbar.make(mDrawer, "Please allow access to your storage", Snackbar.LENGTH_LONG)
.setAction("Allow", view -> ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.READ_EXTERNAL_STORAGE
}, 0)).show();
}
}
作为替代方案,您始终可以创建 Room 数据库,同时强制它不使用预写日志记录:
Room.databaseBuilder(context, db.class, dbName)
.setJournalMode(JournalMode.TRUNCATE)
.build();
首先,必须关闭数据库才能应用 "dbName.db-wal"
文件中的更改。
然后您可以复制包含所有表和最后数据更改的数据库
AppDatabase appDatabase = AppDatabase.getAppDatabase(getApplicationContext());
appDatabase.close();
上面已经回答过了。不需要close/re-open数据库。
我在我的 android 应用程序中使用 MVVM 模式来备份数据库文件以将其上传到 google 驱动器。只想总结对我有用的解决方案:
在您的 DAO 文件中提及以下代码:
@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
在您的存储库文件中提及以下代码:
/* Android database has three files under /data/data/com.package.app/databases/
** test.db, test.db-shm, test.db-wal - those extra files have recent commits.
** To merge data from other shm and wal files to db, run following method - useful before taking backup.
*/
void checkPoint() {
ItemRoomDatabase.databaseWriteExecutor.execute(() -> {
itemDao.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
});
}
在您的 ViewModel 中提及以下内容:
public void checkPoint() {
itemRepository.checkPoint();
}
现在您可以在从 Activity 文件进行备份之前调用此方法
ItemViewModel itemViewModel = new ViewModelProvider(this).get(ItemViewModel.class);
itemViewModel.checkPoint();
我正在使用这个库来备份和重新存储房间数据库,它非常容易使用。
https://github.com/salehyarahmadi/RoomDatabaseBackupAndRestore
感谢 salehyarahmadi
首先需要做的是创建具有适当日志模式的数据库。
Room.databaseBuilder(context, AppDatabase::class.java, name)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.build()
之后需要执行以下检查点查询以确保应用所有未决事务。
为此,需要将以下方法添加到数据库 Dao 接口
interface UserDao {
@RawQuery
fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery?): Single<Int>
}
然后需要使用以下SQL查询来调用该方法
userDao.checkpoint((SimpleSQLiteQuery("pragma wal_checkpoint(full)")))
检查点方法成功后,数据库备份文件终于可以保存了。
最后可以使用以下代码检索数据库备份文件
File(database.openHelper.writableDatabase.path)
然后需要将该文件复制到备份文件位置。
要恢复文件,唯一需要做的就是用备份文件覆盖数据库文件(可以使用上面的代码片段检索)。
您可以在我的博客上阅读更多详细信息
https://androidexplained.github.io/android/room/2020/10/03/room-backup-restore.html
为了更具体地回答您的问题,这就是我在我的一个应用程序中备份房间数据库的方式。
1-检查读取/写入外部存储的权限。 2-关闭您的 RoomDatabase。在我的例子中,AppDatabase 指的是一个包含最初构建房间数据库的逻辑的单例。 AppDatabase.getInstance(this@MainActivity) 获取单例的当前实例及其从 RoomDatabase 扩展的当前数据库 class。 3-然后基本上调用 dbInstance.close().
private fun createBackup() {
val db = AppDatabase.getInstance(this@MainActivity)
db.close()
val dbFile: File = getDatabasePath(DATABASE_NAME)
val sDir = File(Environment.getExternalStorageDirectory(), "Backup")
val fileName = "Backup (${getDateTimeFromMillis(System.currentTimeMillis(), "dd-MM-yyyy-hh:mm")})"
val sfPath = sDir.path + File.separator + fileName
if (!sDir.exists()) {
sDir.mkdirs()
}
val saveFile = File(sfPath)
if (saveFile.exists()) {
Log.d("LOGGER ", "File exists. Deleting it and then creating new file.")
saveFile.delete()
}
try {
if (saveFile.createNewFile()) {
val bufferSize = 8 * 1024
val buffer = ByteArray(bufferSize)
var bytesRead: Int
val saveDb: OutputStream = FileOutputStream(sfPath)
val indDb: InputStream = FileInputStream(dbFile)
do {
bytesRead = indDb.read(buffer, 0, bufferSize)
if (bytesRead < 0)
break
saveDb.write(buffer, 0, bytesRead)
} while (true)
saveDb.flush()
indDb.close()
saveDb.close()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
您必须在
中包含保存文件try {
//backup process
}
} catch (e: Exception) {
e.printStackTrace()
}
按照错误发生的顺序排列并避免应用程序崩溃。
并从 currentTimeMillis 获取日期 使用这个功能
fun getDateTimeFromMillis(millis: Long, pattern: String): String {
val simpleDateFormat = SimpleDateFormat(pattern, Locale.getDefault()).format(Date())
return simpleDateFormat.format(millis)
}
Resoting Db 的代码 正在将文件对象传递给 Uri.fromFile
try {
val fileUri: Uri = Uri.fromFile(file)
val inputStream = contentResolver.openInputStream(fileUri)
println("restoring ")
restoreDatabase(inputStream);
inputStream?.close()
} catch (e: IOException) {
println( e.message)
e.printStackTrace()
}
** 或以 activity 开头的结果返回结果 **
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 12 && resultCode == RESULT_OK && data != null) {
Uri fileUri = data.getData();
try {
assert fileUri != null;
InputStream inputStream = getContentResolver().openInputStream(fileUri);
restoreDatabase(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
restoreDatabase 函数
private fun restoreDatabase(inputStreamNewDB: InputStream?) {
val db = AppDatabase.getInstance(this@MainActivity)
db.close()
val oldDB = getDatabasePath(DATABASE_NAME)
if (inputStreamNewDB != null) {
try {
copyFile(inputStreamNewDB as FileInputStream?, FileOutputStream(oldDB))
println("restore success")
} catch (e: IOException) {
Log.d("BindingContextFactory ", "ex for is of restore: $e")
e.printStackTrace()
}
} else {
Log.d("BindingContextFactory ", "Restore - file does not exists")
}
}
现在您需要将文件从备份复制到真实的数据库文件中 使用复制文件
@Throws(IOException::class)
fun copyFile(fromFile: FileInputStream?, toFile: FileOutputStream) {
var fromChannel: FileChannel? = null
var toChannel: FileChannel? = null
try {
fromChannel = fromFile?.channel
toChannel = toFile.channel
fromChannel?.transferTo(0, fromChannel.size(), toChannel)
} finally {
try {
fromChannel?.close()
} finally {
toChannel?.close()
}
}
}