Java旧项目迁移至KMM:SQLDelight复用已有.db数据库方案咨询
旧Java项目迁移至KMM+SQLDelight的数据库迁移方案
针对新用户直接使用新库、老用户从Assets目录的database.db迁移数据的需求,下面是一套跨平台通用的实现方案:
整体思路
- 新用户:直接通过SQLDelight初始化全新数据库
- 老用户:
- 将Assets中只读的旧数据库复制到应用可读写的沙盒目录
- 读取旧库中的所有数据
- 将数据批量写入SQLDelight创建的新数据库
- 标记迁移完成,后续直接使用新库
具体实现步骤
1. 配置SQLDelight数据库
先在KMM项目中定义与旧库结构匹配的SQLDelight Schema,确保字段类型完全兼容(比如旧Java SQLite的INTEGER对应SQLDelight的Long,TEXT对应String)。
举个例子,假设旧库有user表:
-- 旧Java项目的user表结构 CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER );
对应的SQLDelight Schema:
-- src/commonMain/sqldelight/com/example/database/AppDatabase.sq CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER ); -- 定义基础查询和插入方法 selectAllUsers: SELECT * FROM user; insertUser: INSERT INTO user(name, age) VALUES (?, ?);
2. 迁移状态标记
为避免重复执行迁移逻辑,需添加一个迁移状态标记。推荐在SQLDelight库中新增专门的状态表,操作更统一:
-- 添加到AppDatabase.sq CREATE TABLE migration_status ( migrated INTEGER NOT NULL DEFAULT 0 -- 0=未迁移,1=已迁移 );
3. Android端迁移逻辑
3.1 复制Assets旧数据库到可读写目录
Android Assets目录下的文件是只读的,必须先复制到应用沙盒的数据库目录:
// 可放在DatabaseFactory类中 private fun copyOldDbFromAssets(context: Context) { val oldDbFile = context.getDatabasePath("old_database.db") if (oldDbFile.exists()) return context.assets.open("database.db").use { input -> FileOutputStream(oldDbFile).use { output -> input.copyTo(output) } } }
3.2 读取旧数据并写入新库
用Android原生SQLiteDatabase连接旧库,查询数据后通过SQLDelight事务批量插入新库:
private fun migrateOldData(context: Context, newDb: AppDatabase) { val oldDbPath = context.getDatabasePath("old_database.db").absolutePath val oldDb = SQLiteDatabase.openDatabase(oldDbPath, null, SQLiteDatabase.OPEN_READONLY) val cursor = oldDb.rawQuery("SELECT id, name, age FROM user", null) val userQueries = newDb.userQueries // 事务确保数据一致性 userQueries.transaction { while (cursor.moveToNext()) { val name = cursor.getString(cursor.getColumnIndexOrThrow("name")) val age = cursor.getInt(cursor.getColumnIndexOrThrow("age")) userQueries.insertUser(name, age.toLong()) } } // 标记迁移完成 newDb.migrationStatusQueries.updateMigrated(1) // 清理资源 cursor.close() oldDb.close() // 可选:迁移成功后删除旧库节省空间 File(oldDbPath).delete() }
3.3 数据库初始化入口
fun createDatabase(context: Context): AppDatabase { val driver = AndroidSqliteDriver(AppDatabase.Schema, context, "new_database.db") val db = AppDatabase(driver) // 检查迁移状态 val migrated = db.migrationStatusQueries.getMigrated().executeAsOneOrNull() ?: 0 if (migrated == 0) { copyOldDbFromAssets(context) migrateOldData(context, db) } // 新用户直接使用新库,如需初始化默认数据可在此添加 return db }
4. iOS端迁移逻辑(如需支持iOS)
如果KMM项目覆盖iOS端,逻辑与Android类似:
4.1 复制Assets旧库到沙盒
private func copyOldDbFromAssets() { let fileManager = FileManager.default let docsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] let oldDbPath = docsDir.appendingPathComponent("old_database.db") guard !fileManager.fileExists(atPath: oldDbPath.path) else { return } guard let sourcePath = Bundle.main.path(forResource: "database", ofType: "db") else { return } do { try fileManager.copyItem(atPath: sourcePath, toPath: oldDbPath.path) } catch { print("复制旧数据库失败: \(error.localizedDescription)") } }
4.2 迁移数据到新库
用FMDB简化SQLite操作,读取旧库数据后批量插入SQLDelight新库:
private func migrateOldData(to newDb: AppDatabase) { let fileManager = FileManager.default let docsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] let oldDbPath = docsDir.appendingPathComponent("old_database.db").path guard let oldDb = FMDatabase(path: oldDbPath), oldDb.open() else { return } defer { oldDb.close() } guard let resultSet = oldDb.executeQuery("SELECT name, age FROM user", withArgumentsIn: []) else { return } newDb.userQueries.transaction { while resultSet.next() { let name = resultSet.string(forColumn: "name")! let age = resultSet.longLongInt(forColumn: "age") newDb.userQueries.insertUser(name: name, age: age) } } // 标记迁移完成 newDb.migrationStatusQueries.updateMigrated(1) // 可选:删除旧库文件 try? fileManager.removeItem(atPath: oldDbPath) }
4.3 iOS端数据库初始化
func createDatabase() -> AppDatabase { let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let dbPath = docsDir.appendingPathComponent("new_database.db") let driver = SqliteDriver(path: dbPath.path) let db = AppDatabase(driver: driver) let migrated = db.migrationStatusQueries.getMigrated().executeAsOneOrNull() ?? 0 if migrated == 0 { copyOldDbFromAssets() migrateOldData(to: db) } return db }
5. 关键注意事项
- 类型兼容:必须确保旧库字段类型与SQLDelight定义的类型完全匹配,避免数据丢失或插入失败
- 事务保护:迁移数据时一定要用事务,防止中途崩溃导致数据部分插入
- 错误处理:添加异常捕获和日志,方便排查迁移失败的问题
- 测试覆盖:分别测试新用户首次安装、老用户升级两种场景,确保逻辑正常
内容的提问来源于stack exchange,提问作者user238664
相关产品推荐
相关产品推荐

