繁体   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