如何使用 Room 预封装的数据库回调?

2024-03-19

版本 2.3.0-alpha03 Room 有一个prepackagedDatabaseCallback它说:-

  • This callback will be invoked after the pre-package DB is copied but before Room had a chance to open it and therefore before the RoomDatabase.Callback methods are invoked. This callback can be useful for updating the pre-package DB schema to satisfy Room's schema validation.

那么我如何使用它来规避无效架构expected .... found ....?

我可以用它来引入触发器吗,因为房间没有创建触发器的注释?

Note这旨在作为提出并回答你自己的问题 https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/?_ga=2.140654769.2011732958.1616341909-2099202242.1574488815


那么我如何使用它来规避无效架构expected .... found ....?

下面虽然冗长,但显示了一个相应地更正模式的示例。

我可以用它来引入触发器吗,因为房间没有创建触发器的注释?

是的,虽然这尚未经过实际测试,但该示例适合触发器的创建。

有关示例的注释首先,应该指出的是,当最初查看回调时,存在问题。这些现已得到解决BUT需要2.4.0-beta02或更大。问题是:-

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

  • Correctly open pre-package database during PrepackagedDatabaseCallback invocation.

  • The actual pre-package database was not being opened because only the file name was being used when creating the temporary open helper instead of the full path. The framework open helper will only open an existing file if the name of the database is a path (starts with '/') otherwise it create a new file. This changes fixes it by using the absolute file path as name.

The Flow

当控制权传递给回调时,资产数据库已被复制。因此,从理论上讲,这只是更改表和视图(如果有)以匹配房间期望的模式的问题。

试图将房间的期望与房间的发现相匹配常常会让一些人感到沮丧。下面的示例将克服次要/小/经常被忽略的问题。它的工作原理是:-

  1. 删除除表之外的所有非 sqlite 或非 android 组件(即复制的资产数据库中的视图、索引和触发器),
  2. 重命名表以允许创建房间期望的表,
  3. Creating new tables using Room's expected schema,
    1. 从 Room 生成的 java 复制而来
  4. Copying the data from the renamed (asset) tables into the newly created tables
    1. 已使用 INSERT OR IGNORE,因此约束冲突,例如 NOT NULL、UNIQUE、CHECK 不会导致异常(更改INSERT OR IGNORE只是INSERT 失败)
  5. 创建 Room 期望的其他组件(视图、索引和触发器(注意 Room 仅具有 FTS 表的触发器))。
  6. 删除现在多余的重命名表
  7. 执行 VACUUM 来清理数据库,
  8. 最后添加任何触发器。

它确实期望源(资产)具有正确顺序的列,并且 room 具有 NOT NULL 约束的列中不包含空值。但是,当使用 INSERT OR IGNORE 时,不会插入此类行,而不会导致异常。

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

The Code

绝大多数代码都在@Database类中其他数据库。请注意,这应该可以通过一些定制来应对许多数据库(请参阅评论以进行更改):-

@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;
        }
    }
}

工作示例

资产数据库

资产数据库包含3个表,person, company and 公司人员地图

person DDL is

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`))
  • 注意类型DATE v INTEGER对于 dateOfBirth 列,哪个房间不接受,所以预期...发现....

company DDL is

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`))
  • 两者都差不多 房间会接受

公司人员地图 DDL is

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 )
  • 虽然差别很大,但具有可比性,房间可以接受

资产表包含以下数据:-

person

company

公司人员地图

  • 人员 11 未映射到公司。

AllDao is

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

调用活动是:-

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","\n\t");
            }
        }
    }

    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
                + "\n\t 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
        );
    }
}

Result

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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Room 预封装的数据库回调? 的相关文章

随机推荐

  • 在使用 Rails 保存之前将字符串转换为日期

    对 Ruby 很陌生 我已经被困了好几个小时了 到处寻找也找不到答案 所以我使用 Rails gem 的引导日期选择器 因为我更改了日期选择器的日期格式 所以它不会存储在数据库中 猜测这是因为 simple form 输入被用作字符串 以避
  • 如何使用 ajax 请求发送 Twitter OAuth 访问令牌?

    我想在使用 OAuth 通过 用 twitter 签名 进行身份验证后加载用户的主页时间线 我正在使用这个库来处理身份验证部分https github com jmathai twitter async https github com j
  • 登录 WordPress 网站时如何显示用户的用户名?

    当用户登录到我的 WordPress 网站时 我希望我的网站标题显示 登录身份 我不知道如何回显当前用户的用户名 这是我得到的代码
  • 改变python shell的背景颜色

    例如 是否可以将 Python Shell 的背景颜色从白色更改为黑色 我确实找到了如何更改文本颜色 但不知道如何更改背景颜色 我是在Windows下运行的 有什么建议么 如果您指的是 IDLE 我通过此链接执行了这个简单的步骤 适用于 u
  • 使用列表理解在列表元素前面添加前缀

    有一个这样的列表 foo spam bar 使用列表理解是否可以获取此列表作为结果 foo ok foo spam ok spam bar ok bar In 67 alist foo spam bar In 70 prefix elt f
  • 如何使用 JavaScript 打开新选项卡/窗口?

    客观的 我想在新选项卡 窗口中打开 URLEXACT与 target blank 相同的方式 Code 我正在使用触发以下 JavaScript 的 PHP 条件 我的问题 window open 是不一样作为 target blank 超
  • 为什么 Java 源文件要进入目录结构?

    假设我正在创建一个包含以下类的 Java 项目 com bharani ClassOne com bharani ClassTwo com bharani helper HelperOne com bharani helper suppor
  • 如何从泽西岛 1.0 迁移到泽西岛 2.0?

    我正在尝试升级到 Jersey 2 0 但遇到了很多麻烦 因为 Jersey 的 groupIds 和artifactIds 已完全更改 并且我在 中找不到迁移计划泽西岛文档 https jersey java net nonav docu
  • Google Play 商店的 RSS 源

    Apple 应用程序商店为您提供 RSS 源 以便将提交到应用程序商店的最新应用程序发送到您最喜欢的源阅读器中 我想知道 google play 商店是否也有 RSS feed 可以让你做同样的事情 Thanks 不 他们不提供任何供第三方
  • 您能否提供一些与在项目中使用的人工智能相关的主题想法? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • scala slick 一对多集合

    我有一个数据库 其中包含具有一对多注册关系的活动 目标是获取所有活动及其注册列表 通过创建带有注册的活动的笛卡尔积 获取该数据所需的所有数据都已存在 但我似乎找不到一个好的方法来将它正确地放入 scala 集合中 让我们输入 Seq Act
  • 如何为来自react js中的api的动态表单字段设置常规onchange?

    我正在使用 React 中的表单 我面临的挑战是每当我为输入字段设置 onchange 处理程序时 它都会更改我拥有的所有输入字段的值 表单输入字段已映射并且来自 api 我想要的是一个通用的更改处理程序来管理所有输入字段 import R
  • Spring boot中如何配置gson?

    Spring Boot 2 在application yml中 http converters preferred json mapper gson 现在我用自定义设置编写类Gson public class GsonUtil public
  • 在 QML 中获取 $HOME 和/或用户名

    我需要知道如何获取用户的用户名和 或主目录 我用 google 搜索了一段时间 但只能找到 C 或 BASH 的变量 如何获取用户名或主目录 我正在用 QML 写作 这就是我的实现方式 Qml环境变量 h ifndef QMLENVIRON
  • C#5 ReadAsync 和迭代器

    我正在尝试将下面的类转换为延迟返回文件 public class ObservableFile2 IObservable
  • java.lang.IllegalArgumentException:端口超出范围:67001

    我在运行脚本时遇到以下错误 该脚本正在尝试调用 Web 服务 并且成功调用了 Web 服务 最后 它也打印了成功的消息 只是在中间抛出了一些带有 LOG UTILS 的错误 请大家看看并提出建议 java lang IllegalArgum
  • 在react.js中渲染新元素onClick

    我是反应新手 正在尝试在单击时渲染一个新元素 var LoginButton React createClass clickHandle function this rememberMe active localforage getItem
  • NextJS 公共环境变量不适用于 Azure 应用服务

    I have a NEXT PUBLIC environment variable in my NextJS application It has been set in my env local file and working corr
  • 如何检查文件是否为空

    我有一个文本文件 如何检查是否为空 gt gt gt import os gt gt gt os stat file st size 0 True
  • 如何使用 Room 预封装的数据库回调?

    版本 2 3 0 alpha03 Room 有一个prepackagedDatabaseCallback它说 This callback will be invoked after the pre package DB is copied