如何使用Room预打包的DatabaseCallback? [英] How do I use Room's prepackagedDatabaseCallback?

查看:11
本文介绍了如何使用Room预打包的DatabaseCallback?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在2.3.0-alpha03版本中,房间有一个prepackagedDatabaseCallback,它显示:-

  • 此回调将在复制预包数据库之后、Room有机会打开它之前调用,因此在调用RoomDatabase.Callback方法之前调用。此回调对于更新预打包数据库架构以满足Room的架构验证非常有用。

那么我如何使用它来规避无效的架构预期的.找到.?

我可以用它来介绍触发器吗,因为房间没有用于创建触发器的批注?

注意此为ask, and answer, your own question

推荐答案

那么我如何使用它来规避无效的架构预期的.找到.?

下面虽然冗长,但显示了相应更正架构的示例。

我可以用它来介绍触发器吗,因为房间没有用于创建触发器的批注?

是的,虽然这实际上没有经过测试,但是该示例满足了触发器的创建要求。

重新评级示例的说明 首先,应该注意的是,最初查看回调时存在问题。现在已解决这些问题,需要2.4.0-beta02或更高版本。问题是:-

  1. 不允许在预打包的数据库中使用版本0(通常会出现这种情况)
  2. 传递给PrePackagedDatabaseCallback的空数据库
  3. PrePackagedDatabaseCallback中应用的数据库更新丢失/撤消
  • 应用的修复是

  • 在PrePackagedDatabaseCallback调用期间正确打开预包数据库。

  • 没有打开实际的预包数据库,因为在创建临时打开帮助器时只使用了文件名,而没有使用完整路径。框架打开帮助器仅在数据库名称为路径(以‘/’开头)时才会打开现有文件,否则它会创建一个新文件。此更改通过使用绝对文件路径作为名称来修复它。

流程

将控制权传递给回调时,资产数据库已被复制。因此,从理论上讲,这只是修改表和视图(如果有的话)以匹配空间所需的架构的问题。

经常试图将房间期望的内容与房间发现的内容进行匹配,这会让一些人感到沮丧。下面的示例将克服小问题/小问题/经常遗漏的问题。它的工作方式:-

  1. 丢弃除表以外的所有非sqlite或非Android组件(即从复制的资产数据库中删除视图、索引和触发器),
  2. 重命名表以允许文件室预期创建的表,
  3. 使用Room的预期架构创建新表,
    1. 复制自Room生成的java
  4. 将数据从重命名的(资产)表复制到新创建的表中
    1. 已使用INSERT或IGNORE,因此约束冲突(如NOT NULL、UNIQUE、CHECK)不会导致异常(将INSERT OR IGNORE更改为仅INSERT 将失败)
  5. 创建Room期望的其他组件(视图、索引和触发器(请注意,Room只有用于FTS表的触发器))。
  6. 正在删除现在冗余的重命名表
  7. 清理数据库,
  8. 并最终添加任何触发器。

它确实期望源(资产)具有正确顺序的列,空值不包括在空间具有非空值约束的列中。但是,由于使用了INSERT或IGNORE,因此不会插入此类行,而不会导致异常。

除了从生成的Java复制Come代码之外,该过程是自动化的,应该可以处理大多数/许多资产,而无需修改。

代码

绝大多数代码位于@Database类OtherDatabase中。请注意,这应该可以处理许多数据库,只需稍加调整(请参阅要进行更改的注释):-

@SuppressLint({"Range"}) /*<<<<< used due to bug/issue with getColumnIndex introduced with SDK 31 */
@Database(
        entities = {Person.class, Company.class, CompanyPersonMap.class}, //<<<<< CHANGED ACCORDINGLY
        version = OtherDatabase.DATABASE_VERSION, /* note due to shared usage of DBVERSION OtherDatabase used <<<<< CHANGE ACCORDINGLY */
        exportSchema = false /* change to suit */
)
@TypeConverters({AllTypeConverters.class})
abstract class OtherDatabase extends RoomDatabase {
    public static final String DATABASE_NAME = "otherprepackageddatabasecallbacktest.db"; //<<<<< CHANGE AS REQUIRED
    public static final int DATABASE_VERSION = 1; //<<<<< CHANGE AS REQUIRED
    public static final String ASSET_FILE_NAME = "prepackageddatabasecallbacktest.db"; //<<<<< CHANGED AS REQUIRED
    /**
     *  sqlite_master table and column names !!!!!DO NOT CHANGE!!!!!
     */
    private static final String SQLITE_MASTER_TABLE_NAME = "sqlite_master";
    private static final String SQLITE_MASTER_COL_NAME = "name";
    private static final String SQLITE_MASTER_COL_TYPE = "type";
    private static final String SQLITE_MASTER_COL_TABLE_NAME = "tbl_name";
    private static final String SQLITE_MASTER_COL_SQL = "sql";

    abstract AllDao getAllDao(); //<<<<< CHANGE ACCORDINGLY
    private static volatile OtherDatabase instance = null; //<<<<< CHANGE ACCORDINGLY
    public static OtherDatabase /*<<<< CHANGE ACCORDINGLY */ getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context, OtherDatabase.class, OtherDatabase.DATABASE_NAME)
                    .allowMainThreadQueries() /*<<<<< USED FOR BREVITY CONVENIENCE */
                    .createFromAsset(ASSET_FILE_NAME, prePkgDbCallback) /* 2nd parameter is the THE CALL BACK to invoke */
                    .build();
        }
        return instance;
    }

    /* THE CALLBACK  */
    static final PrepackagedDatabaseCallback prePkgDbCallback = new PrepackagedDatabaseCallback() {
        final static String assetTablePrefix = "asset_"; /* prefix used for renamed tables - should not need to be changed */
        private List<SQLiteMasterComponent> sqLiteMasterComponentArray; /* store for sqlite_master extract */
        @Override
        public void onOpenPrepackagedDatabase(@NonNull SupportSQLiteDatabase db) {
            super.onOpenPrepackagedDatabase(db);
            sqLiteMasterComponentArray = buildComponentsList(db); /* gets relevant rows from sqlite_master */
            dropNonTableComponents(sqLiteMasterComponentArray,db); /* everything except the tables */
            renameTableComponents(sqLiteMasterComponentArray, assetTablePrefix,db); /* rename the tables using prefix */
            /*<<<<< TAILOR (the db.execSQL's below) AS APPROPRIATE - SEE COMMENTS THAT FOLLOW >>>>>*/
            /* copied from the @Database classes generated java e.g. leave indexes till later
                _db.execSQL("CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`))");
                _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_firstName` ON `Person` (`firstName`)");
                _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_lastName` ON `Person` (`lastName`)");
                _db.execSQL("CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))");
                _db.execSQL("CREATE TABLE IF NOT EXISTS `company_person_map` (`companyid_map` INTEGER NOT NULL, `personid_map` INTEGER NOT NULL, PRIMARY KEY(`companyid_map`, `personid_map`), FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )");
                _db.execSQL("CREATE INDEX IF NOT EXISTS `index_company_person_map_personid_map` ON `company_person_map` (`personid_map`)");
             */
            /* Create the tables as per Room definitions - ***** CREATE TABLES COPIED FROM GENERATED JAVA *****
                only indexes, views, triggers (for FTS) should be done after the data has been copied so :-
                    data is loaded faster as no index updates are required.
                    triggers don't get triggered when loading the data which could result in unexpected results
             */
            db.execSQL("CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`));");
            db.execSQL("CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))");
            db.execSQL("CREATE TABLE IF NOT EXISTS `company_person_map` (`companyid_map` INTEGER NOT NULL, `personid_map` INTEGER NOT NULL, PRIMARY KEY(`companyid_map`, `personid_map`), FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )");

            copyData(sqLiteMasterComponentArray, assetTablePrefix,db); /* copy the data from the renamed asset tables to the newly created Room tables */

            /* Create the other Room components - ***** CREATE ? COPIED FROM GENERATED JAVA *****
                Now that data has been copied create other Room components indexes, views and triggers
                (triggers would only be for FTS (full text search))
                again sourced from generated Java
             */
            db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_firstName` ON `Person` (`firstName`)");
            db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_lastName` ON `Person` (`lastName`)");
            db.execSQL("CREATE INDEX IF NOT EXISTS `index_company_person_map_personid_map` ON `company_person_map` (`personid_map`)");
            dropRenamedTableComponents(sqLiteMasterComponentArray, assetTablePrefix,db); /* done with the renamed tables so drop them */
            db.execSQL("VACUUM"); /* cleanup the database */
            createTriggers(sqLiteMasterComponentArray,db); /* create any triggers */
        }
    };

    static int dropNonTableComponents(List<SQLiteMasterComponent> components, SupportSQLiteDatabase db) {
        int rv = 0;
        for(SQLiteMasterComponent c: components) {
            if (!c.type.equals("table") ) {
                db.execSQL("DROP " +  c.type + " IF EXISTS " + c.name);
                rv++;
            }
        }
        return rv;
    }

    static int dropRenamedTableComponents(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
        int rv = 0;
        int maxForeignKeyCount = 0;
        for (SQLiteMasterComponent c: components) {
            if (c.type.equals("table") && c.foreignKeyCount > maxForeignKeyCount) {
                maxForeignKeyCount = c.foreignKeyCount;
            }
        }
        for (int i= maxForeignKeyCount; i >= 0; i--) {
            for (SQLiteMasterComponent c: components) {
                if (c.type.equals("table") && c.foreignKeyCount == i) {
                    db.execSQL("DROP " + c.type + " IF EXISTS " + prefix + c.name);
                    rv++;
                }
            }
        }
        return rv;
    }

    static int renameTableComponents(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
        int rv = 0;
        db.execSQL("PRAGMA foreign_keys = ON"); // Just in case turn foreign keys on
        for(SQLiteMasterComponent c: components) {
            if (c.type.equals("table")) {
                db.execSQL("ALTER TABLE " + c.name + " RENAME TO " + prefix + c.name);
                rv++;
            }
        }
        return rv;
    }

    /*
        NOTE tables with fewest Foreign Key definitions done first
        NOTE makes an assumption that this will not result in FK conflicts
        TODO should really be amended to ensure that a table with FK's is only attempted when all of it's parent tables have been loaded
     */
    static int copyData(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
        int rv = 0;
        int maxForeignKeyCount = 0;
        for (SQLiteMasterComponent c: components) {
            if (c.type.equals("table") && c.foreignKeyCount > 0) {
                maxForeignKeyCount = c.foreignKeyCount;
            }
        }
        for (int i=0; i <= maxForeignKeyCount; i++) {
            for (SQLiteMasterComponent c: components) {
                if (c.type.equals("table") && c.foreignKeyCount == i) {
                    db.execSQL("INSERT OR IGNORE INTO " + c.name + " SELECT * FROM " + prefix + c.name + ";");
                    rv++;
                }
            }
        }
        return rv;
    }

    static int createTriggers(List<SQLiteMasterComponent> components, SupportSQLiteDatabase db) {
        int rv = 0;
        for (SQLiteMasterComponent c: components) {
            if (c.type.equals("trigger")) {
                //TODO should really check if the sql includes IF NOT EXISTSand if not add IF NOT EXISTS
                db.execSQL(c.sql);
                rv++;
            }
        }
        return rv;
    }

    /**
     * Build the list of required SQLiteMasterComponents to save having to access
     * sqlite_master many times.
     * @param db the SupportSQliteDatabase to access
     * @return the list of SQliteMasterComponents extracted
     */
    @SuppressLint("Range")
    static List<SQLiteMasterComponent> buildComponentsList(SupportSQLiteDatabase db) {
        final String FOREIGN_KEY_FLAG_COLUMN =  "foreign_key_flag";
        ArrayList<SQLiteMasterComponent> rv = new ArrayList<>();
        Cursor csr = db.query("SELECT *," +
                /* Column to indicate wherther or not FK constraints appear to have been defined
                 *  NOTE!! can be fooled
                 *       e.g. if a column is defined as `my  badly named FOREIGN KEY column ` ....
                 * */
                "(" +
                "instr(" + SQLITE_MASTER_COL_SQL + ",'FOREIGN KEY') > 0) + " +
                "(instr(" + SQLITE_MASTER_COL_SQL + ",' REFERENCES ')> 0) " +
                "AS " + FOREIGN_KEY_FLAG_COLUMN + " " +
                "FROM " + SQLITE_MASTER_TABLE_NAME + " " +
                /* do not want any sqlite tables or android tables included */
                "WHERE lower(" + SQLITE_MASTER_COL_NAME + ") NOT LIKE 'sqlite_%' AND lower(" + SQLITE_MASTER_COL_NAME + ") NOT LIKE 'android_%'");
        while (csr.moveToNext()) {
            SQLiteMasterComponent component = new SQLiteMasterComponent(
                    csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_NAME)),
                    csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_TYPE)),
                    csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_TABLE_NAME)),
                    csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_SQL)),
                    csr.getInt(csr.getColumnIndex(FOREIGN_KEY_FLAG_COLUMN)),
                    0);
            if (csr.getInt(csr.getColumnIndex(FOREIGN_KEY_FLAG_COLUMN)) > 0) {
                component.foreignKeyCount = component.getForeignKeyCount(db);
            }
            rv.add(component);
        }
        csr.close();
        return (List<SQLiteMasterComponent>) rv;
    }

    /**
     *  Class to hold a row from sqlite_master
     */
    private static class SQLiteMasterComponent {
        private String name;
        private String type;
        private String owningTable;
        private String sql;
        private int foreignKeyFlag = 0;
        private int foreignKeyCount = 0;

        SQLiteMasterComponent(String name, String type, String owningTable, String sql, int foreignKeyFlag, int foreignKeyCount) {
            this.name = name;
            this.type = type;
            this.owningTable = owningTable;
            this.sql = sql;
            this.foreignKeyFlag = foreignKeyFlag;
            this.foreignKeyCount = foreignKeyCount;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getOwningTable() {
            return owningTable;
        }

        public void setOwningTable(String owningTable) {
            this.owningTable = owningTable;
        }

        public String getSql() {
            return sql;
        }

        public void setSql(String sql) {
            this.sql = sql;
        }

        public int getForeignKeyFlag() {
            return foreignKeyFlag;
        }

        public void setForeignKeyFlag(int foreignKeyFlag) {
            this.foreignKeyFlag = foreignKeyFlag;
        }

        public boolean isForeignKey() {
            return this.foreignKeyFlag > 0;
        }

        public int getForeignKeyCount() {
            return foreignKeyCount;
        }

        public void setForeignKeyCount(int foreignKeyCount) {
            this.foreignKeyCount = foreignKeyCount;
        }

        /**
         * Retrieve the number of rows returned by PRAGMA foreign_key_list
         * @param db    The SupportSQLiteDatabase to access
         * @return      The number of rows i.e. number of Foreign Key constraints
         */
        private int getForeignKeyCount(SupportSQLiteDatabase db) {
            int rv =0;
            Cursor csr = db.query("SELECT count(*) FROM pragma_foreign_key_list('" + this.name + "');");
            if (csr.moveToFirst()) {
                rv = csr.getInt(0);
            }
            csr.close();
            return rv;
        }
    }
}

工作示例

资产数据库

资产数据库包含人员公司COMPANY_PERSON_MAP3个表

人员DDL为

CREATE TABLE person (personid INTEGER PRIMARY KEY, firstName TEXT, lastName TEXT, middleNames TEXT, dateOfBirth DATE);

房间预期:-

CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`))
  • 注意dateOfBirth列的日期v整数类型,房间不接受该类型,因此预期.找到.

公司DDL为

CREATE TABLE company (companyid INTEGER PRIMARY KEY, companyName TEXT, city TEXT, state TEXT, country TEXT, notes TEXT);

房间预期:-

CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))
  • 两者都是可以接受的可比房间

COMPANY_PERSON_MAPDDL

CREATE TABLE company_person_map (
companyid_map INTEGER NOT NULL REFERENCES company(companyid) ON DELETE CASCADE ON UPDATE CASCADE,
personid_map INTEGER NOT NULL REFERENCES person(personid) ON DELETE CASCADE ON UPDATE CASCADE, 
PRIMARY KEY (companyid_map, personid_map));

房间预期:-

CREATE TABLE IF NOT EXISTS `company_person_map` (
`companyid_map` INTEGER NOT NULL,`personid_map` INTEGER NOT NULL, 
PRIMARY KEY(`companyid_map`, `personid_map`), 
FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , 
FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )
  • 虽然差异很大,但具有可比性,房间可以接受

资产表包含以下数据:-

人员

公司

COMPANY_PERSON_MAP

  • 人员%11未映射到公司。

所有DAO

  • 未使用任何插入,也未使用所有查询。

调用活动为:-

public class MainActivity extends AppCompatActivity {

    OtherDatabase dbOther;
    AllDao daoOther;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbOther = OtherDatabase.getInstance(this);
        daoOther = dbOther.getAllDao();
        for(Person p: daoOther.getAllPeople()) {
            Log.d("DBINFO",
                    "Person is " + p.firstName
                            + " " + p.middleNames
                            + " " + p.lastName
                            + " Date of Birth is " + p.dateOfBirth
                            + " ID is " + p.id
            );
        }
        for(CompanyWithPeople cwp: dao.getCompanyWithPeople()) {
            logCompany(cwp.company,"DBINFO","");
            for(Person p: cwp.person) {
                logPerson(p,"DBINFO","
	");
            }
        }
    }

    void logCompany(Company c, String tag, String lineFeed) {
        Log.d(tag,lineFeed + "Company is " + c.companyName
                + " Location is " + c.city + ", " + c.state + ", " + c.country
                + " ID is " + c.companyId
                + "
	 Notes: " + c.notes
        );
    }

    void logPerson(Person p, String tag, String lineFeed) {
        Log.d(tag, lineFeed + p.firstName + " " + p.middleNames
                        + " " + p.lastName + " Date of Birth is " + p.dateOfBirth
                        + " ID is " + p.id
        );
    }
}

结果

2021-11-28 19:56:01.928 D/DBINFO: Person is Robert John Smith Date of Birth is Sat Dec 27 17:46:33 GMT+10:00 1969 ID is 1
2021-11-28 19:56:01.929 D/DBINFO: Person is Julie Mary Armstrong Date of Birth is Mon Jan 12 23:22:04 GMT+10:00 1970 ID is 2
2021-11-28 19:56:01.929 D/DBINFO: Person is Andrea Susan Stewart Date of Birth is Mon Jan 05 04:56:09 GMT+10:00 1970 ID is 3
2021-11-28 19:56:01.929 D/DBINFO: Person is Mary Belinda Allway Date of Birth is Mon Jan 12 00:15:21 GMT+10:00 1970 ID is 4
2021-11-28 19:56:01.929 D/DBINFO: Person is Lisa Elizabeth Brooks Date of Birth is Sat Jan 03 03:51:21 GMT+10:00 1970 ID is 5
2021-11-28 19:56:01.930 D/DBINFO: Person is Stephen Colin Cobbs Date of Birth is Tue Jan 06 14:01:55 GMT+10:00 1970 ID is 6
2021-11-28 19:56:01.930 D/DBINFO: Person is Breane Cath Davidson Date of Birth is Thu Jan 01 22:30:14 GMT+10:00 1970 ID is 7
2021-11-28 19:56:01.930 D/DBINFO: Person is Trevor Arthur Frankston Date of Birth is Sat Jan 10 03:47:02 GMT+10:00 1970 ID is 8
2021-11-28 19:56:01.930 D/DBINFO: Person is George Howard Erksine Date of Birth is Sun Jan 11 00:47:02 GMT+10:00 1970 ID is 9
2021-11-28 19:56:01.930 D/DBINFO: Person is Uriah Stanley Jefferson Date of Birth is Mon Dec 29 19:11:31 GMT+10:00 1969 ID is 10
2021-11-28 19:56:01.931 D/DBINFO: Person is Valerie Alana Singleton Date of Birth is Thu Jan 01 11:45:07 GMT+10:00 1970 ID is 11
2021-11-28 19:56:01.931 D/DBINFO: Person is Vladimir Oscar Whitworth Date of Birth is Sat Jan 10 00:29:45 GMT+10:00 1970 ID is 12
2021-11-28 19:56:01.936 D/DBINFO: Company is Allbright Construction Location is Sydney, NSW, Australia ID is 1
         Notes: 
2021-11-28 19:56:01.936 D/DBINFO:   Julie Mary Armstrong Date of Birth is Mon Jan 12 23:22:04 GMT+10:00 1970 ID is 2
2021-11-28 19:56:01.936 D/DBINFO:   Mary Belinda Allway Date of Birth is Mon Jan 12 00:15:21 GMT+10:00 1970 ID is 4
2021-11-28 19:56:01.937 D/DBINFO:   Stephen Colin Cobbs Date of Birth is Tue Jan 06 14:01:55 GMT+10:00 1970 ID is 6
2021-11-28 19:56:01.937 D/DBINFO:   Trevor Arthur Frankston Date of Birth is Sat Jan 10 03:47:02 GMT+10:00 1970 ID is 8
2021-11-28 19:56:01.937 D/DBINFO:   Uriah Stanley Jefferson Date of Birth is Mon Dec 29 19:11:31 GMT+10:00 1969 ID is 10
2021-11-28 19:56:01.937 D/DBINFO:   Vladimir Oscar Whitworth Date of Birth is Sat Jan 10 00:29:45 GMT+10:00 1970 ID is 12
2021-11-28 19:56:01.937 D/DBINFO: Company is Dextronics Location is Slough, Berkshire, England ID is 2
         Notes: 
2021-11-28 19:56:01.937 D/DBINFO:   Robert John Smith Date of Birth is Sat Dec 27 17:46:33 GMT+10:00 1969 ID is 1
2021-11-28 19:56:01.938 D/DBINFO:   Andrea Susan Stewart Date of Birth is Mon Jan 05 04:56:09 GMT+10:00 1970 ID is 3
2021-11-28 19:56:01.938 D/DBINFO:   Lisa Elizabeth Brooks Date of Birth is Sat Jan 03 03:51:21 GMT+10:00 1970 ID is 5
2021-11-28 19:56:01.938 D/DBINFO:   Breane Cath Davidson Date of Birth is Thu Jan 01 22:30:14 GMT+10:00 1970 ID is 7
2021-11-28 19:56:01.938 D/DBINFO:   George Howard Erksine Date of Birth is Sun Jan 11 00:47:02 GMT+10:00 1970 ID is 9

这篇关于如何使用Room预打包的DatabaseCallback?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆