如何在首次运行时填充Android Room数据库表?

63

SQLiteOpenHelper类中有一个onCreate(SQLiteDatabase ...)方法,我用它来向数据库表中插入一些初始数据。

是否有一种方法可以在首次运行应用程序时将某些数据插入到Room数据库表中?


你可以提供一个预填充的数据库,然后在其上进行工作。 - Phantômaxx
2
这里有一篇好的文章:https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1 - Michael Vescovo
7个回答

142

更新

有三种方式可以实现此操作:重要:查看此处以获取迁移详细信息

1- 从导出的资产模式填充数据库

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build();

2- 从文件中填充您的数据库

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromFile(new File("mypath"))
    .build();

3- 您可以使用 RoomDatabase.Callback 在数据库创建后或每次打开数据库时运行脚本,该类可在最新版本的 Room library 中使用。

您需要实现 RoomDatabase.CallbackonCreateonOpen 方法,并按下面所示将其添加到 RoomDatabase.Builder 中。

yourDatabase = Room.databaseBuilder(context, YourDatabase.class, "your db")
    .addCallback(rdc)
    .build();

RoomDatabase.Callback rdc = new RoomDatabase.Callback() {
    public void onCreate (SupportSQLiteDatabase db) {
        // do something after database has been created
        
    }
    public void onOpen (SupportSQLiteDatabase db) {
        // do something every time database is open
     
    }
};

参考资料

您可以在RoomDatabase.Callback方法中使用Room DAO本身来填充数据库。有关完整的示例,请参见Android分页和Room示例

   RoomDatabase.Callback dbCallback = new RoomDatabase.Callback() {
        public void onCreate(SupportSQLiteDatabase db) {
            Executors.newSingleThreadScheduledExecutor().execute(new Runnable() {
                @Override
                public void run() {
                   getYourDB(ctx).yourDAO().insertData(yourDataList);
                }
            });
        }
    };

8
这个应该被标记为正确答案。谢谢! - Imanol
6
我遇到一个问题,即在.build()时回调没有被触发,只有在第一次真正的读/写操作时才会触发 如此处所述 - Maxim
1
我正在尝试这种方法,但是我遇到了一个错误:java.lang.IllegalStateException: getDatabase called recursively。 - Eduardo Corona
@EduardoCorona,你可能已经将getYourDb部分添加到了onOpen()方法中。自然地,这将导致无尽的递归调用。 - Alex Semeniuk
@Arnav Rao,“mypath”文件长什么样?“myapp.db”呢? - Leonardo Sibela
显示剩余2条评论

30
我尝试使用RoomDatabase.Callback,正如Arnav Rao建议的那样,但是要使用回调,您不能使用DAO,因为回调在数据库构建之前创建。您可以使用db.insert和content values,但我认为那可能不正确。所以在进一步研究了一下之后 - 花费了我很长时间 - 但当我浏览Google提供的示例时,我实际上找到了答案。

https://github.com/googlesamples/android-architecture-components/blob/master/PersistenceContentProviderSample/app/src/main/java/com/example/android/contentprovidersample/data/SampleDatabase.java

请参见第52行和第71行的方法 - 在这里,您可以看到在构建数据库实例之后,下一行调用了一个方法,该方法检查数据库中是否有任何记录(使用DAO),如果它为空,则插入最初的数据(再次使用DAO)。

希望这对任何卡住的人有所帮助:)


6
非常感谢您分享。这似乎是正确的方法。 - szuuuken

9

创建数据库后,您可以填充表格,确保操作在单独的线程上运行。您可以按照以下类来预先填充第一次的表。

AppDatabase.kt

@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

    companion object {

        // For Singleton instantiation
        @Volatile private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                    .addCallback(object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            //pre-populate data
                            Executors.newSingleThreadExecutor().execute {
                                instance?.let {
                                    it.userDao().insertUsers(DataGenerator.getUsers())
                                }
                            }
                        }
                    })
                    .build()
        }
    }
}

DataGenerator.kt

class DataGenerator {

    companion object {
        fun getUsers(): List<User>{
            return listOf(
                User(1, "Noman"),
                User(2, "Aayan"),
                User(3, "Tariqul")
            )
        }
    }

}

5

我试过多种方法来做这件事,但都没有成功。

首先,我尝试使用“addMigrations”方法向Room添加迁移实现,但发现它只在数据库升级时运行,而不是在创建时运行。

然后,我尝试使用“openHelperFactory”方法将SQLiteOpenHelper实现传递给Room。但是,为了绕过Room的包级访问修饰符,我创建了大量类,但最终放弃了努力。我还尝试了子类化Room的FrameworkSQLiteOpenHelperFactory,但是它的构造函数的包级访问修饰符不支持这一点。

最后,我创建了一个IntentService来填充数据,并从我的Application子类的onCreate方法中调用它。这种方法可行,但更好的解决方案应该是Sinigami在此页面其他地方提到的跟踪器问题即将推出的修复。

Darryl

[2017年7月19日添加]

该问题似乎已在Room 1.0.0 Alpha 5中得到解决。此版本添加了一个回调到RoomDatabase,使您可以在首次创建数据库时执行代码。请参阅:

https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.Callback.html


4
@Provides
@Singleton
LocalDatabase provideLocalDatabase(@DatabaseInfo String dbName, Context context) {
    return Room.databaseBuilder(context, LocalDatabase.class, dbName)
            .addCallback(new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);
                    db.execSQL("INSERT INTO id_generator VALUES(1, 1, 1);");
                }
            })
//                .addMigrations(LocalDatabase.MIGRATION_1_2)
            .build();
}

2

3种预填充数据库的方法
前两种是从资产和文件复制,具体描述可参考这里
第三种方法是在创建数据库后通过编程实现

Room.databaseBuilder(context, Database::class.java, "app.db")
    // ...
    // 1
    .createFromAsset(...)
    // 2
    .createFromFile(...)
    // 3
    .addCallback(DatabaseCallback())
    .build()

这里是手动填写

class DatabaseCallback : RoomDatabase.Callback() {

    override fun onCreate(db: SupportSQLiteDatabase) = db.run {
        // Notice non-ui thread is here
        beginTransaction()
        try {
            execSQL(...)
            insert(...)
            update(...)
            delete(...)
            setTransactionSuccessful()
        } finally {
            endTransaction()
        }
    }
}

0

我也曾为这个问题苦恼,这个解决方案对我很有效:

// build.gradle

    def room_version = "2.2.5"

    // Room
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version"

在你的 App 类中:
// virtually create the db
        val db = Room.databaseBuilder(
            appContext, AppDatabase::class.java,
            Res.getString(R.string.dbname)
        ).createFromAsset(Res.getString(R.string.source_db_name)).build()

        // first call to db really creates the db on filesystem
        db.query("SELECT * FROM " + Room.MASTER_TABLE_NAME, null)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接