簡體   English   中英

如何用數據預填充現有數據庫?

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

我正在構建的應用程序目前有一個Workout database

Workout DB有一個Workout table和一個WoroutSets table

這兩個表中的數據是通過用戶輸入插入(保存)的。

這就是存儲一些數據的地方。

順便說一句,我想將名為WorkoutList pre-populated data放入此Workout DB中。

我為此查閱了文檔

我導出Workout.db並在 DB Browser 中為 SQLite 預填充了數據。

我們將根據文檔使用createFromAsset("Workout.db") (還沒試過)

但是,我擔心的是現有應用程序的Work DB與添加了WorkoutList table的Workout DB之間是否存在沖突。

假設您想保留每個應用程序用戶輸入的workout s 和workoutsets ,那么您不希望使用createFromAsset覆蓋它們。

相反,我懷疑您想要做的是引入一個新的鍛煉列表表,該表根據作為資產提供的數據庫在鍛煉列表中填充預定義/預先存在的行。 在這種情況下,您不想使用createFromAsset方法(盡管您可能會從資產創建第二個數據庫,將其附加到原始數據庫,然后合並數據——這會比需要的更復雜)。

您還必須考慮如何處理新安裝,在這種情況下將沒有現有的用戶輸入workoutworkoutsets ,在這種情況下您可以使用createFromAsset方法。 但是,您不會想要任何其他用戶的workoutworkoutsets行。

基於這個假設,也許可以考慮這個演示,它引入了一個新表(鍛煉列表),其數據是從資產中檢索的,在其他表(鍛煉和鍛煉集)中維護原始用戶數據,但是對於新安裝的應用程序,從資產中創建數據庫.

  • 模式是編造的,因此很可能與您的不同,但原則適用。
  • Java已經用過了,改成Kotlin用不了多久

鍛煉

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

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

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 (新表)

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

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 @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();
            }
        }
    };
}
  • 這是為了允許使用任一版本輕松切換/運行應用程序,只需進行兩個更改即可作為應用程序的舊版本或新版本運行。
    • 作為版本 1 運行 DATABASE_VERSION(在 MainActivity 中)設置為 1
    • 並且WorkoutSet class 在 @Database 注釋中被注釋掉了。
  • 如果數據庫已經存在,則 Mirgration 處理資產中的數據副本,否則對於新文件,則使用createFromAssets從資產中復制數據庫。

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));
    }
}
  • 請注意,在創建要加載到 SQlite 工具的數據庫時,wdb.close() 未被注釋。

testit.db SQLite 數據庫文件經過修改以引入新的workoutList表。 這首先從版本 1 的 App 運行中復制,然后添加新表(根據從 WorkoutDataabase_Impl class 的createAllTables方法復制的SQL在生成的 java(也就是根據 Room 的期望的確切表))。

在此處輸入圖像描述

  • 請注意,確實應刪除這些行,因為它們是特定於用戶的。 相反-WRONG已添加到數據的末尾(有助於證明某些觀點)

在此處輸入圖像描述

  • 如上

新表

在此處輸入圖像描述

Navicat 是 SQLite 使用的工具而不是 DB Browser。

Run 1以版本 1 運行以填充數據庫

  • 數據庫版本 = 1
  • @Database(entities = {Workout.class,WorkoutSet.class/*, WorkoutList.class*/ /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
    • 即 WorkoutList 表已被排除

應用檢查顯示:-

在此處輸入圖像描述

在此處輸入圖像描述

  • 重要的是沒有 WorkoutList

Run 2版本 2 和引入新的workoutlist

  • 數據庫版本 = 2
  • @Database(entities = {Workout.class,WorkoutSet.class,WorkoutList.class /*<<<<<<<<<<為 V2 添加 */},exportSchema = false,version = MainActivity.DATABASE_VERSION)
    • 即現在引入了WorkoutList

應用檢查顯示:-

在此處輸入圖像描述

  • 保留舊行(根據資產,它們沒有 -WRONG)

在此處輸入圖像描述

  • 保留舊行

在此處輸入圖像描述

  • 新表,根據資產填充

在 VERSION 2運行 3 次新安裝(應用程序卸載后)

在此處輸入圖像描述

  • 可以看出,存在資產行中錯誤保留的行(顯然您會在現實生活中刪除這些行,但將它們保留在表明它們來自資產而不是由活動中的代碼生成)

在此處輸入圖像描述

  • 同樣地

在此處輸入圖像描述

結論

因此,新安裝的應用程序通過createFromAsset正確地復制了資產數據庫,而如果遷移,則僅從資產復制鍛煉列表表中的新數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM