简体   繁体   English

如何用数据预填充现有数据库?

[英]How to pre-populate an existing DB with data?

The app I'm building currently has a Workout database .我正在构建的应用程序目前有一个Workout database

Workout DB has a Workout table and a WoroutSets table . Workout DB有一个Workout table和一个WoroutSets table

The data in these two tables is inserted(saved) through user input.这两个表中的数据是通过用户输入插入(保存)的。

And that's where some data is stored.这就是存储一些数据的地方。

By the way, I want to put pre-populated data called WorkoutList into this Workout DB .顺便说一句,我想将名为WorkoutList pre-populated data放入此Workout DB中。

I consulted the docs for this.我为此查阅了文档

I exported Workout.db and pre-populated it with data in DB Browser for SQLite.我导出Workout.db并在 DB Browser 中为 SQLite 预填充了数据。

And we are going to use createFromAsset("Workout.db") as per the docs.我们将根据文档使用createFromAsset("Workout.db") (Haven't tried yet) (还没试过)

However, what I am concerned about is whether there is a conflict between the Work DB of the existing app and the Workout DB to which the WorkoutList table has been added.但是,我担心的是现有应用程序的Work DB与添加了WorkoutList table的Workout DB之间是否存在冲突。

Assuming that you want to preserve each app users workout s and workoutsets s that they have input then you would not want to overwrite them by using createFromAsset .假设您想保留每个应用程序用户输入的workout s 和workoutsets ,那么您不希望使用createFromAsset覆盖它们。

Rather I suspect that what you want to do is introduce a new workoutlist table that is populated with predefined/pre-existing rows in the workoutlist as per a database supplied as an asset.相反,我怀疑您想要做的是引入一个新的锻炼列表表,该表根据作为资产提供的数据库在锻炼列表中填充预定义/预先存在的行。 In this case you do not want to use the createFromAsset method (although you could potentially have a second database created from the asset, attach it to the original and then merge the data - this would be more complex than it need be).在这种情况下,您不想使用createFromAsset方法(尽管您可能会从资产创建第二个数据库,将其附加到原始数据库,然后合并数据——这会比需要的更复杂)。

You also have to consider how to handle new installs, in which case there will be no existing user input workout s and workoutsets s, in which case you could use createFromAsset method.您还必须考虑如何处理新安装,在这种情况下将没有现有的用户输入workoutworkoutsets ,在这种情况下您可以使用createFromAsset方法。 However, you would not want any other user's workout s and workoutsets s rows.但是,您不会想要任何其他用户的workoutworkoutsets行。

Based upon this assumption perhaps consider this demo that introduces a new table (workoutlist) whose data is retrieved from an asset maintaining the original user data in the other tables (workout and workoutset) but for a new install of the App creates database from the asset.基于这个假设,也许可以考虑这个演示,它引入了一个新表(锻炼列表),其数据是从资产中检索的,在其他表(锻炼和锻炼集)中维护原始用户数据,但是对于新安装的应用程序,从资产中创建数据库.

  • the schema is made up so will very likely differ from yours but the principle applies.模式是编造的,因此很可能与您的不同,但原则适用。
  • Java has been used but it would take little to change it to Kotlin Java已经用过了,改成Kotlin用不了多久

Workout锻炼

@Entity
class Workout {
    @PrimaryKey
    Long workoutId=null;
    String workoutName;

    Workout(){};
    @Ignore
    Workout(String workoutName) {
        this.workoutId=null;
        this.workoutName=workoutName;
    }
}

WorkoutSet (plural not used but easily changed) WorkoutSet (复数未使用但很容易更改)

@Entity
class WorkoutSet {
    @PrimaryKey
    Long workoutSetId=null;
    String workoutSetName;
    long workoutIdMap;

    WorkoutSet(){}
    @Ignore
    WorkoutSet(String workoutSetName, long parentWorkout) {
        this.workoutSetId=null;
        this.workoutSetName = workoutSetName;
        this.workoutIdMap = parentWorkout;
    }
}

WorkkoutList (the new table) WorkkoutList (新表)

@Entity
class WorkoutList {
    @PrimaryKey
    Long workoutListId=null;
    String workoutListName;
}

AllDAO (just for completeness) AllDAO (只是为了完整性)

@Dao
abstract class AllDAO {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract long insert(Workout workout);
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract long insert(WorkoutSet workoutSet);
    @Query("SELECT count(*) FROM workout")
    abstract long getNumberOfWorkouts();
}

WorkoutDatabase the @Database annotated class WorkoutDatabase @Database注解为class

@Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
abstract class WorkoutDatabase extends RoomDatabase {
    abstract AllDAO getAllDAO();

    private static Context passed_context;

    private static volatile WorkoutDatabase INSTANCE;
    static WorkoutDatabase getInstance(Context context) {

        passed_context = context;
        if (INSTANCE==null && MainActivity.DATABASE_VERSION == 1) {
            INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
                    .allowMainThreadQueries()
                    .build();
        }
        if (INSTANCE ==null && MainActivity.DATABASE_VERSION > 1) {
            INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
                    .allowMainThreadQueries()
                    .createFromAsset(MainActivity.DATABASE_ASSET_NAME) /* so new App installs use asset */
                    .addMigrations(MIGRATION_1_TO_2) /* to handle migration */
                    .build();
        }
        return INSTANCE;
    }

    static Migration MIGRATION_1_TO_2 = new Migration(1,2) {
        @SuppressLint("Range")
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {

            /* Create the new table */
            database.execSQL("CREATE TABLE IF NOT EXISTS `WorkoutList` (`workoutListId` INTEGER, `workoutListName` TEXT, PRIMARY KEY(`workoutListId`))");


            /* Cater for copying the data from the asset */
            String tempDBName = "temp_" + MainActivity.DATABASE_NAME;  /* name of the temporary/working database NOT an SQLITE TEMP database */
            String newTableName = "workoutlist"; /* The table name */
            String qualifiedNewTableName = tempDBName + "." + newTableName; /* The fully qualified new table name for the attached temp/wrk db */
            String tempDBPath = passed_context.getDatabasePath(MainActivity.DATABASE_NAME).getParent() + File.separator + tempDBName; /* path to temp/wrk db */
            try {
                /* Copy the asset to a second DB */
                InputStream asset = passed_context.getAssets().open(MainActivity.DATABASE_ASSET_NAME); /* open the asset */
                File tempDB_File = new File(tempDBPath); /* File for temp/wrk database */
                OutputStream tempdb = new FileOutputStream(tempDB_File); /* now an output stream ready for the copy */
                int bufferLength = 1024 * 8; /* length of buffer set to 8k */
                byte[] buffer = new byte[bufferLength]; /* the buffer for the copy */
                /* copy the temp/wrk database from the asset to it's location */
                while(asset.read(buffer) > 0) {
                    tempdb.write(buffer);
                }
                /* clean up after copy */
                tempdb.flush();
                tempdb.close();
                asset.close();

                /*Use the temporary/working database to populate the actual database */
                /* Issues with WAL file change because migration is called within a transaction  as per */
                /* java.lang.IllegalStateException: Write Ahead Logging (WAL) mode cannot be enabled or disabled while there are transactions in progress. .... */
                /* SO COMMENTED OUT */
                //database.execSQL("ATTACH DATABASE '" + tempDBPath + "' AS " + tempDBName);
                //database.execSQL("INSERT INTO " + newTableName + " SELECT * FROM " + qualifiedNewTableName);
                //database.execSQL("DETACH " + tempDBName);

                /* Alternative to ATTACH */
                SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(tempDB_File.getPath(),null,SQLiteDatabase.OPEN_READONLY);
                Cursor csr = assetdb.query(newTableName,null,null,null,null,null,null);
                ContentValues cv = new ContentValues();
                while (csr.moveToNext()) {
                    cv.clear();
                    for (String s: csr.getColumnNames()) {
                        cv.put(s,csr.getString(csr.getColumnIndex(s)));
                    }
                    database.insert(newTableName,SQLiteDatabase.CONFLICT_IGNORE,cv);
                }
                assetdb.close();

                tempDB_File.delete(); /* delete the temporary/working copy of the asset */
            } catch (Exception e) {
                /* handle issues here e.g. no asset, unable to read/write an so on */
                e.printStackTrace();
            }
        }
    };
}
  • This has been written to allow easy switching/running of the App with either version, simply two changes to run as old or new version of the App.这是为了允许使用任一版本轻松切换/运行应用程序,只需进行两个更改即可作为应用程序的旧版本或新版本运行。
    • to run as version 1 DATABASE_VERSION (in MainActivity) is set to 1作为版本 1 运行 DATABASE_VERSION(在 MainActivity 中)设置为 1
    • AND the WorkoutSet class is commented out in the @Database annotation.并且WorkoutSet class 在 @Database 注释中被注释掉了。
  • the Mirgration handles the copy of the data from the asset if the database already exists, otherwise for a new file then createFromAssets is used to copy the database from the asset.如果数据库已经存在,则 Mirgration 处理资产中的数据副本,否则对于新文件,则使用createFromAssets从资产中复制数据库。

MainActivity the activity code that does something with the database to ensure that it is opened/accessed MainActivity对数据库执行某些操作以确保打开/访问数据库的活动代码

public class MainActivity extends AppCompatActivity {

    public static final String DATABASE_NAME = "workout.db";
    public static final int DATABASE_VERSION = 2;
    public static final String DATABASE_ASSET_NAME = "testit.db"/*DATABASE_NAME*/ /* could be different */;

    WorkoutDatabase wdb;
    AllDAO dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wdb = WorkoutDatabase.getInstance(this);
        dao = wdb.getAllDAO();

        if (dao.getNumberOfWorkouts() < 1) addInitialData();
        //wdb.close(); // FORCE close so WAL is full checkpointed (i.e. no -wal or -shm)
    }

    private void addInitialData() {
        String prefix = String.valueOf(System.currentTimeMillis()); // to differentiate devices
        dao.insert(new Workout(prefix + "-W001"));
        dao.insert(new Workout(prefix + "-W002"));
        dao.insert(new WorkoutSet(prefix + "-WS001",1));
        dao.insert(new WorkoutSet(prefix + "-WS002",2));
    }
}
  • note the wdb.close() is uncommented when creating the database to be loaded into the SQlite tool.请注意,在创建要加载到 SQlite 工具的数据库时,wdb.close() 未被注释。

testit.db the SQLite database file as modified to introduce the new workoutList table. testit.db SQLite 数据库文件经过修改以引入新的workoutList表。 This first copied from a run of the App at Version 1 and then adding the new table (according SQL copied from the createAllTables method of WorkoutDataabase_Impl class in the generated java (aka the exact table as per Room's expectations)).这首先从版本 1 的 App 运行中复制,然后添加新表(根据从 WorkoutDataabase_Impl class 的createAllTables方法复制的SQL在生成的 java(也就是根据 Room 的期望的确切表))。

在此处输入图像描述

  • Note really the rows should be deleted as they are user specific.请注意,确实应删除这些行,因为它们是特定于用户的。 Instead -WRONG has been added to the end of the data (helps to prove the some points)相反-WRONG已添加到数据的末尾(有助于证明某些观点)

在此处输入图像描述

  • as above如上

The new table新表

在此处输入图像描述

Navicat was the SQLite tool used rather than DB Browser. Navicat 是 SQLite 使用的工具而不是 DB Browser。

Run 1 running at version 1 to populate the database Run 1以版本 1 运行以填充数据库

  • DATABASE_VERSION = 1数据库版本 = 1
  • @Database(entities = {Workout.class,WorkoutSet.class/*, WorkoutList.class*/ /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
    • ie the WorkoutList table has been excluded即 WorkoutList 表已被排除

App Inspection shows:-应用检查显示:-

在此处输入图像描述

and

在此处输入图像描述

  • Impotantly no WorkoutList table重要的是没有 WorkoutList

Run 2 version 2 and introduction of new workoutlist table Run 2版本 2 和引入新的workoutlist

  • DATABASE_VERSION = 2数据库版本 = 2
  • @Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION) @Database(entities = {Workout.class,WorkoutSet.class,WorkoutList.class /*<<<<<<<<<<为 V2 添加 */},exportSchema = false,version = MainActivity.DATABASE_VERSION)
    • ie WorkoutList now introduced即现在引入了WorkoutList

App Inspection shows:-应用检查显示:-

在此处输入图像描述

  • old rows retained (they do not have -WRONG as per the asset)保留旧行(根据资产,它们没有 -WRONG)

在此处输入图像描述

  • old rows retained保留旧行

在此处输入图像描述

  • the new table, populated as per the asset新表,根据资产填充

Run 3 new install (after App uninstalled) at VERSION 2在 VERSION 2运行 3 次新安装(应用程序卸载后)

在此处输入图像描述

  • as an be seen the incorrectly left in the assets rows exist (obviously you would delete the rows in real life but leaving them in shows that they are from the asset rather than generated by the code in the activity)可以看出,存在资产行中错误保留的行(显然您会在现实生活中删除这些行,但将它们保留在表明它们来自资产而不是由活动中的代码生成)

在此处输入图像描述

  • likewise同样地

在此处输入图像描述

Conclusion结论

So the new install of the App correctly copies the asset database via createFromAsset whilst if migrating only the new data in the workoutlist table is copied from the asset.因此,新安装的应用程序通过createFromAsset正确地复制了资产数据库,而如果迁移,则仅从资产复制锻炼列表表中的新数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM