简体   繁体   中英

How to prepopulate database using Room? I don't see tutorials that explains in details using java

I checked this link: https://developer.android.com/training/data-storage/room/prepopulate and I don't know the step by step of doing it.

I don't know what will I write in the db file, I still need some examples. I don't know how to access the prepopulated db, like what functions/methods will I use. I really have no idea. Please help.

Most tutorials are done in kotlin.

you can create a database using other tools like "db browser for SQLite", there you will be able to create tables and fill them with data and save the database as.db file, which you will be able to move to your android project.

You may wish to consider the following, even though it may appear to be the wrong order (it may overcome perplexing issues with the dreaded Expected/Found issue that is often associated with trying to adapt room's entities to cater for an existing database)

  • If you already have a populated pre-packaged database you may find it easier to add steps to create the Room expected tables, copy the data from the existing tables and drop them afterwards (still probably less perplexing than trying to ascertain the nuances of Room) .
  1. Design and create your schema for the database.
  2. Using the schema create the entities for Room, that is a class annotated with @Entity per table.
  3. Create an appropriate @Database annotated abstract class that includes ALL of the @Entity annotated class in the entities = parameter of the @Database annotation.
  4. Compile (successfully) the project.
  5. In the Android View of Android Studio look at the generated java there will be a class that has the same names as the @Database annotated class but suffixed with _Impl . In this class there will be a method called createAllTables and you will see an execSQL statement for each table and any supplementary indexes
    • note if you use Views then you need to create the @View annotated classes and follow the similar procedure. However, Views are not common though and hence they have not been included.
  6. Use the SQL from the execSQ L statements to create your tables in favourite SQL tool (eg DB Browser for SQLite, Navicat, DBeaver).
  7. Populate the database using your SQLite Tool.
  8. Close and Save the database (it is suggested that you then Open the database and check that it is as expected and then Close and Save the database again (some tools sometimes don't close the database))
  9. Create the assets folder in your project.
  10. Copy the saved database file into the assets folder.
  11. add/amend where you undertake the Room's build method, to precede it with .createFromAsset("the_database_file_name_including_extension_if_one")
    • this tells Room, when building the database, if the database does not exist, to copy the database from the assets folder.
  12. Save and compile and then the App.

Following the above order, especially using Room to generate the SQL, will circumvent having to understand the nuances of Room in regards to column type affinities and column constraints.

  • for example you CANNOT have a column type that is not INTEGER, TEXT, REAL or BLOB.

Here's an example with a single table named Table1 that has 6 columns

The Table1 @Entity annotated class:-

@Entity /*(
        primaryKeys = {"column_name","more_columns_for_a_composite_primary"},
        tableName = "alternative_table_name"
)*/
class Table1 {
    /* in ROOM every table MUST have a column either annotated with @PrimaryKey
        or have a primary key specified in the primaryKeys parameter of the @Entity annotation
     */
    @PrimaryKey
    Long id = null; /* this will be a typically id column that will, if no value is specified, be generated (must be Long not long) */
    String text_column;
    @NonNull
    String notnull_text_column;
    double real_column;
    byte[] blob_column;
    @NonNull
    @ColumnInfo(index = true)
    double notnull_real_column_also_indexed;

}

The @Database annotated class, TheDatabase ,including singleton for getting an instance.

@Database(entities = {Table1.class}, version = 1, exportSchema = false)
abstract class TheDatabase extends RoomDatabase {

   private volatile static TheDatabase instance = null;

   public static TheDatabase getInstance(Context context) {
      if (instance == null) {
         instance = Room.databaseBuilder(context,TheDatabase.class,"the_database.db")
                 .allowMainThreadQueries() /* for convenience and brevity */
                 .createFromAsset("the_database.db")
                 .build();
      }
      return instance;
   }
}

With the above, the project can be compiled (Ctrl+F9), and then the generated java associated with the @Database annotated class can be located eg

在此处输入图像描述

Within the class there will be a method name createAllTables:-

@SuppressWarnings({"unchecked", "deprecation"})
public final class TheDatabase_Impl extends TheDatabase {
  @Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("CREATE TABLE IF NOT EXISTS `Table1` (`id` INTEGER, `text_column` TEXT, `notnull_text_column` TEXT NOT NULL, `real_column` REAL NOT NULL, `blob_column` BLOB, `notnull_real_column_also_indexed` REAL NOT NULL, PRIMARY KEY(`id`))");
        _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Table1_notnull_real_column_also_indexed` ON `Table1` (`notnull_real_column_also_indexed`)");
        _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
        _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a6ca75e8ee6037ad13c258cdc0405ef1')");
      }

      @Override
      public void dropAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("DROP TABLE IF EXISTS `Table1`");
        if (mCallbacks != null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
            mCallbacks.get(_i).onDestructiveMigration(_db);
          }
        }
      }
      ....

As can be seen there is an execSQ L for the table1 table, another for the index (as index = true was specified in the @ColumnInfo annotation).

  • the room_master_table is used by room to store a hash of the schema, this is not needed, and should NOT be created in the pre-packaged database that will be copied into the assets folder.

    • The hash will change if the schema changes (the @Entity annotated classes)
  • Nuances

    • if you look closely, you will see that both the real_column and the notnull_real_column have the NOT NULL constraint, but only the latter has the @NonNull annotation. This is because double, is a primitive and ALWAYS has a value, so Room implicitly applies the NOT NULL constraint. If the NOT NULL constraint is not coded when creating the pre-packaged database then after the asset has been copied, when running the App, an exception will occur as the database that was found (the one copied from the asset) will be different (in Room's view) from what Room expected (the schema according to the @Entity annotated classed defined in the list of entities in the @Database annotation). Hence , why it is suggested to create the schema via room, extract the generated SQL and use this to create the pre-packaged database. This ensures that the database schema is as expected.
    • Note this is just one example of the nuances

Continuing with a working example

One thing that often trips new users of SQLite and also Room is that when you instantiate the Database class, is that, it does not then create or open the database. It is not until an attempt is made to access the database (changing or extracting data from the database) that the database is opened and if necessary created and in the case of a pre-populated database copied from the asset (or file, normally the former).

As such, in preparation, for this a (can be one or more), an interface or abstract class annotated with @Dao is created. In this case AllDAO as per:-

@Dao
abstract class AllDAO {
    @Insert
    abstract long insert(Table1 table1);
    @Query("SELECT * FROM table1")
    abstract List<Table1> getAllTable1s();
}
  • using either the insert or the getAllTable1s would access the database.

The @Dao annotated class(es) have to be known/defined to Room, typically the @Database class includes this so the TheDatabase class could then be:-

@Database(entities = {Table1.class}, version = 1, exportSchema = false)
abstract class TheDatabase extends RoomDatabase {
   abstract AllDAO getAllDAO(); //<<<<< ADDED to allow use of the database
   private volatile static TheDatabase instance = null;

   public static TheDatabase getInstance(Context context) {
      if (instance == null) {
         instance = Room.databaseBuilder(context,TheDatabase.class,"the_database.db")
                 .allowMainThreadQueries() /* for convenience and brevity */
                 .createFromAsset("the_database.db")
                 .build();
      }
      return instance;
   }
}

So the App is virtually ready (using it in an activity will be dealt with later).

Now the pre-packaged database can be perpared/built using an SQLite tool (Navicat for SQLite has been used in this case, it shouldn't matter which).

A connection is made and opened, this detailing where the database file will be stored. (see the tool's help if needed). In this case the database is named SOQuestions (already exists):-

在此处输入图像描述

New Query is clicked, and the SQL for the user defined tables is pasted, as well as the indexes. eg:-

在此处输入图像描述

So the table(s) and indexes now exist but are unpopulated. So now to populate the database by inserting some records. A query will be used (in this case as it's only an example queries won't be saved, they could if desired).

So the existing SQL is deleted and replaced with (not deletes all rows, so it is rerunnable) and then run:-

在此处输入图像描述

The resultant data being:-

在此处输入图像描述

The database should be saved. It is suggested that the database/connection is closed and then reopened to check that the data has been saved and then finally closed again (this was done).

The database is now ready to be copied into the assets folder (which currently doesn't exist). So create the assets folder in the project (back to Android Studio). File/Directory was used to select src\main\assets:-

在此处输入图像描述

to get:-

在此处输入图像描述

The file is copied, from Windows Explorer (right click on the file and copy)

在此处输入图像描述

and pasted (right click on the assets folder in Andriod Studio and Paste), renaming it to the_database.db (the database name, as per the createFromAsset (could use soquestions.db as the asset file name) )

在此处输入图像描述

resulting in:-

在此处输入图像描述

Now to running the App by using the database in an activity (note that for brevity and convenience this is run on the main thread).

The activity code:-

public class MainActivity extends AppCompatActivity {

    TheDatabase dbInstance;
    AllDAO dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbInstance = TheDatabase.getInstance(this); /* get DB Instance */
        dao = dbInstance.getAllDAO(); /* get the appropriate Dao */
        logAllRowsFromTable1("PRENEWDATA"); /* WILL INITIATE THE ASSET COPY (if DB does not exist) */
        Table1 newTable1Row = new Table1();
        newTable1Row.text_column = "a new row";
        newTable1Row.blob_column = new byte[30];
        newTable1Row.notnull_text_column = " the new nottnull_text_column";
        newTable1Row.real_column = 4444.55555;
        newTable1Row.notnull_real_column_also_indexed = 7777.8888;
        dao.insert(newTable1Row); /* IF NOT INITIATED ABOVE WILL INITIATE THE ASSET COPY (if DB does not exist)*/
        logAllRowsFromTable1("POSTNEWDATA");
    }

    void logAllRowsFromTable1(String suffix) {
        for (Table1 t: dao.getAllTable1s()) {
            Log.d("DB-" + suffix,
                    "ID is " + t.real_column
                    + "\n\tTEXT_COLUMN is " + t.text_column
                    + "\n\t NOTNULL_TEXT_COLUMN is " + t.notnull_text_column
                    + "\n\t REAL_COLUMN is " + t.real_column
                    + "\n\t NOTNULL_REAL_COLUMN... is " + t.notnull_real_column_also_indexed
                    /* not doing the blob so as not to complicate matters */
            );
        }
    }

This will first output some of the data, for all rows, from the pre-packaged database to the log. It will then add a new run (each run, it is only a demo/example) and then output some of the data for all rows, from the updated (new row) database.

eg :-

2022-04-22 11:00:43.689 D/DB-PRENEWDATA: ID is 10.3333
        TEXT_COLUMN is some text
         NOTNULL_TEXT_COLUMN is some notnull text
         REAL_COLUMN is 10.3333
         NOTNULL_REAL_COLUMN... is 3.1
2022-04-22 11:00:43.689 D/DB-PRENEWDATA: ID is 11.3333
        TEXT_COLUMN is null
         NOTNULL_TEXT_COLUMN is more not null text
         REAL_COLUMN is 11.3333
         NOTNULL_REAL_COLUMN... is 4.1
         
         
2022-04-22 11:00:43.692 D/DB-POSTNEWDATA: ID is 10.3333
        TEXT_COLUMN is some text
         NOTNULL_TEXT_COLUMN is some notnull text
         REAL_COLUMN is 10.3333
         NOTNULL_REAL_COLUMN... is 3.1
2022-04-22 11:00:43.692 D/DB-POSTNEWDATA: ID is 11.3333
        TEXT_COLUMN is null
         NOTNULL_TEXT_COLUMN is more not null text
         REAL_COLUMN is 11.3333
         NOTNULL_REAL_COLUMN... is 4.1
2022-04-22 11:00:43.692 D/DB-POSTNEWDATA: ID is 4444.55555
        TEXT_COLUMN is a new row
         NOTNULL_TEXT_COLUMN is  the new nottnull_text_column
         REAL_COLUMN is 4444.55555
         NOTNULL_REAL_COLUMN... is 7777.8888
  • blank lines added to distinguish between the two sets of output

Android Studio's App Inspection can be used to see the actual data:-

在此处输入图像描述

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