简体   繁体   中英

How to pre-populate an existing DB with data?

The app I'm building currently has a Workout database .

Workout DB has a Workout table and a 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 .

I consulted the docs for this.

I exported Workout.db and pre-populated it with data in DB Browser for SQLite.

And we are going to use createFromAsset("Workout.db") as per the docs. (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.

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 .

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).

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. However, you would not want any other user's workout s and workoutsets s rows.

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

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)

@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)

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

AllDAO (just for completeness)

@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

@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
    • AND the WorkoutSet class is commented out in the @Database annotation.
  • 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.

MainActivity the activity code that does something with the database to ensure that it is opened/accessed

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.

testit.db the SQLite database file as modified to introduce the new workoutList table. 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)).

在此处输入图像描述

  • 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)

在此处输入图像描述

  • as above

The new table

在此处输入图像描述

Navicat was the SQLite tool used rather than DB Browser.

Run 1 running at version 1 to populate the database

  • DATABASE_VERSION = 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

App Inspection shows:-

在此处输入图像描述

and

在此处输入图像描述

  • Impotantly no WorkoutList table

Run 2 version 2 and introduction of new workoutlist table

  • DATABASE_VERSION = 2
  • @Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
    • ie WorkoutList now introduced

App Inspection shows:-

在此处输入图像描述

  • old rows retained (they do not have -WRONG as per the asset)

在此处输入图像描述

  • old rows retained

在此处输入图像描述

  • the new table, populated as per the asset

Run 3 new install (after App uninstalled) at VERSION 2

在此处输入图像描述

  • 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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