当前位置: 首页 > 知识库问答 >
问题:

Android Room-如何在每次运行应用程序时重置自动生成的表主键

薛华奥
2023-03-14

我使用Room来保存数据
我有一个实体,它有一个自动生成(autoGenerate)的主键,模拟票证系统。在每次应用程序运行时,我都需要此键从0开始。

实体:

@Entity
public class SequenceAction {

    @PrimaryKey(autoGenerate = true)
    private Integer sequenceId;
    private String actionType;
    private String extraInfo;
    //getters & setters
}

初始化:

// init sequenceAction object
// run with executor(sequenceId is automatically set on insert to table):
AppDatabase.getInstance(getContext()).sequenceActionDao().save(sequenceAction);

我尝试过的事情:

我使用AppDatabase.getInstance(getApplication ationContext()). clearAllTables();清除退出时的表,但这不会重置键起始索引,而是从上次运行时停止的地方开始。

我还没有找到一种使用Room来实现这一点的方法,因此我正在尝试使用传递给Dao中RawQuery方法的SimpleSQLiteQuery:

//Dao
@RawQuery()
Integer init(SimpleSQLiteQuery query);

//Passed query
new SimpleSQLiteQuery("...query...");

我尝试了以下查询:

  1. "ALTER TABLE SequextActionAUTO_INCREMENT=0"

我收到一个错误(我用“自动增量”尝试了这个,同样的错误):

Android数据库sqlite。SQLiteException:near“AUTO_INCREMENT”:编译时出现语法错误(代码1):ALTER TABLE SequenceAction AUTO_INCREMENT=0

可能是因为,正如这个问题/答案所述,SQLite中没有自动增量关键字,而是声明为INTEGER PRIMARYKEY的列将自动增量。

没有错误,但索引也没有重置。

正如这里所建议的:

"UPDATESQLITE_SEQUENCESET seq=-1 WHERE name='SequextAction'"

没有错误,但没有效果。

错误(可能是因为SQLite不支持TRUNCATE命令):

Android数据库sqlite。SQLiteException:near“TRUNCATE”:编译时出现语法错误(代码1):TRUNCATE表“SequenceAction”;

没有错误,没有效果。

共有3个答案

顾俊楚
2023-03-14

以米凯特的话为榜样。

我认为这是可行的:

        fun clearAndResetAllTables(): Boolean {
        if (db == null) return false

        // reset all auto-incrementalValues
        val query = SimpleSQLiteQuery("DELETE FROM sqlite_sequence")

        db!!.beginTransaction()
        return try {
            db!!.clearAllTables()
            db!!.query(query)
            db!!.setTransactionSuccessful()
            true
        } catch (e: Exception){
            false
        } finally {
            db!!.endTransaction()
        }
    }
康秋月
2023-03-14

在尝试了很多方法之后。终于这对我起作用了!

public static void truncateTable(Context context, SupportSQLiteOpenHelper openHelper, String tableName) {
            SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
                    context.getDatabasePath(openHelper.getDatabaseName()),
                    null
            );

            if (database != null) {
                database.execSQL(String.format("DELETE FROM %s;", tableName));
                database.execSQL("UPDATE sqlite_sequence SET seq = 0 WHERE name = ?;", new String[]{tableName});
            }
        }

实施:

truncateTable(getContext(), yourRoomDatabase.getOpenHelper(), "your_table_name");
慕乐语
2023-03-14

为了在退出但是时清除表,这不会重置键起始索引,而是从上次运行时停止的地方开始。

....

“从sqlite_序列中删除,其中name='sequence Action'”没有错误,但是索引也不会重置。

您必须删除SequextAction表中的所有行,并从sqlite_sequence中删除相应的行。

也就是说,当使用AUTOINCREMENT关键字时,将使用不同的算法。这大致如下:-

找到其中的最高值-a)表的值存储在sqlite_sequence和-b)最高rowid值

另一种选择是不使用自动递增关键字,而只使用??整数主键(其中??表示列名)。

您仍然会有一个唯一的id,它是rowidcoulmn的别名,但不能保证它会一直增加。AUTOINCREment确实保证增加唯一的id,但不能保证单调增加唯一的rowid...

在每次应用程序运行时,我都需要此键从0开始。

但是,SQLite会将第一个值设置为1而不是0。

以下内容确实有效,正如您在AUTOINCREMENT中看到的(尽管有点像黑客):-

DROP TABLE IF EXISTS SequenceAction;
DROP TRIGGER IF EXISTS use_zero_as_first_sequence;
CREATE TABLE IF NOT EXISTS SequenceAction (id INTEGER PRIMARY KEY AUTOINCREMENT, otherdata TEXT);
CREATE TRIGGER IF NOT EXISTS use_zero_as_first_sequence AFTER INSERT ON SequenceAction
    BEGIN 
        UPDATE SequenceAction SET id = id - 1 WHERE id = new.id;
    END
;
INSERT INTO SequenceAction VALUES(null,'TEST1'),(null,'TEST2'),(null,'TEST3');
SELECT * FROM SequenceAction;
-- RESET and RESTART FROM 0
DELETE FROM SequenceAction;
DELETE FROM sqlite_sequence WHERE name = 'SequenceAction';
INSERT INTO SequenceAction VALUES(null,'TEST4'),(null,'TEST5'),(null,'TEST6');
SELECT * FROM SequenceAction
  • 仅测试删除和重新定义所需的两条DROP语句

这导致:-

第一个查询返回:-

第二次返回:-

所以本质上你想要:-

DELETE FROM SequenceAction;
DELETE FROM sqlite_sequence WHERE name = 'SequenceAction';

如果希望编号从0开始而不是从1开始,则也可以使用触发器。

或者,如果不使用自动增量,则可以使用稍微更改的触发器:-

CREATE TRIGGER IF NOT EXISTS use_zero_as_first_sequence 
    AFTER INSERT ON SequenceAction 
    WHEN (SELECT count() FROM SequenceAction) = 1
    BEGIN 
        UPDATE SequenceAction SET id = 0;
    END
;
  • 这只是对第一个插入的行重新编号(然后算法在后续插入中添加1)

然后只需删除SequenceAction表中的所有行,即可重置编号。

根据您的代码以及上面的示例,以下方法似乎可以工作:-

private void resetSequenceAction() {
    SQLiteDatabase dbx;
    String sqlite_sequence_table = "sqlite_sequence";
    long initial_sacount;
    long post_sacount;
    long initial_ssn =0;
    long post_ssn = 0;
    Cursor csr;

    /*
        Need to Create Database and table if it doesn't exist
     */
    File f = this.getDatabasePath(TestDatabase.DBNAME);
    if (!f.exists()) {
        File d = new File(this.getDatabasePath(TestDatabase.DBNAME).getParent());
        d.mkdirs();
        dbx = SQLiteDatabase.openOrCreateDatabase(f,null);
        String crtsql = "CREATE TABLE IF NOT EXISTS " + SequenceAction.tablename + "(" +
                SequenceAction.id_column + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                SequenceAction.actionType_column + " TEXT," +
                SequenceAction.extraInfo_column + " TEXT" +
                ")";
        dbx.execSQL(crtsql);
        /*
           Might as well create the Trigger as well
         */
        String triggerSql = "CREATE TRIGGER IF NOT EXISTS user_zero_as_first_rowid AFTER INSERT ON " +
                SequenceAction.tablename +
                " BEGIN " +
                " UPDATE " + SequenceAction.tablename +
                " SET " +
                SequenceAction.id_column + " = " + SequenceAction.id_column + " - 1 " +
                " WHERE " + SequenceAction.id_column + " = new." + SequenceAction.id_column + ";" +
                " END ";
        dbx.execSQL(triggerSql);

    } else {
        dbx = SQLiteDatabase.openDatabase(this.getDatabasePath(TestDatabase.DBNAME).getPath(),null, Context.MODE_PRIVATE);
    }

    /*
        Add trigger to set id's to 1 less than they were set to
     */
    initial_sacount = DatabaseUtils.queryNumEntries(dbx,SequenceAction.tablename);
    /*
        Delete all the rows at startup
     */
    String deleteAllSequenceIdRowsSql = "DELETE FROM " + SequenceAction.tablename;
    dbx.execSQL(deleteAllSequenceIdRowsSql);
    post_sacount = DatabaseUtils.queryNumEntries(dbx,SequenceAction.tablename);
    /*
        delete the sequence row from the sqlite_sequence table
     */
    csr = dbx.query(sqlite_sequence_table,
            new String[]{"seq"},"name=?",
            new String[]{SequenceAction.tablename},
            null,null,null
    );
    if (csr.moveToFirst()) {
        initial_ssn = csr.getLong(csr.getColumnIndex("seq"));
    }
    String deleteSqlLiteSequenceRow = "DELETE FROM " +
            sqlite_sequence_table +
            " WHERE name = '" + SequenceAction.tablename + "'";
    dbx.execSQL(deleteSqlLiteSequenceRow);
    csr = dbx.query(
            sqlite_sequence_table,
            new String[]{"seq"},
            "name=?",
            new String[]{SequenceAction.tablename},
            null,null,null
    );
    if (csr.moveToFirst()) {
        post_ssn = csr.getLong(csr.getColumnIndex("seq"));
    }
    csr.close();
    Log.d("SEQACTSTATS",
            "Initial Rowcount=" + String.valueOf(initial_sacount) +
                    " Initial Seq#=" + String.valueOf(initial_ssn) +
                    " Post Delete Rowcount =" + String.valueOf(post_sacount) +
                    " Post Delete Seq#=" + String.valueOf(post_ssn)
    );
    dbx.close();
}

初始运行的结果(即不存在数据库):-

D/SEQACTSTATS: Initial Rowcount=0 Initial Seq#=0 Post Delete Rowcount =0 Post Delete Seq#=0

从后续运行(添加了40行之后):-

D/SEQACTSTATS: Initial Rowcount=40 Initial Seq#=40 Post Delete Rowcount =0 Post Delete Seq#=0

添加方法以列出所有行,如下所示:-

private void listAllRows() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            salist = mTestDB.SequenceActionDaoAccess().getAll();
            getSequenceActionList(salist);
        }
    }).start();
}

随着:-

@Override
public void getSequenceActionList(List<SequenceAction> sequenceActionList) {
    for (SequenceAction sa: sequenceActionList) {
        Log.d("SA","ID=" + String.valueOf(sa.getSequenceId()) + " AT=" + sa.getActionType() + " EI=" + sa.getExtraInfo());
    }
}

结果为(第一行为ID=0 AT=X0 EI=Y0,即第一行的ID列为0):-

06-17 02:56:47.867 5526-5554/rt_mjt.roomtest D/SA: ID=0 AT=X0 EI=Y0
    ID=1 AT=X0 EI=Y0
    ID=2 AT=X0 EI=Y0
    ID=3 AT=X0 EI=Y0
    ID=4 AT=X1 EI=Y1
    ID=5 AT=X1 EI=Y1
    ID=6 AT=X1 EI=Y1
    ID=7 AT=X1 EI=Y1
06-17 02:56:47.868 5526-5554/rt_mjt.roomtest D/SA: ID=8 AT=X2 EI=Y2
    ID=9 AT=X2 EI=Y2
    ID=10 AT=X2 EI=Y2
    ID=11 AT=X2 EI=Y2
    ID=12 AT=X3 EI=Y3
    ID=13 AT=X3 EI=Y3
    ID=14 AT=X3 EI=Y3
    ID=15 AT=X3 EI=Y3
    ID=16 AT=X4 EI=Y4
06-17 02:56:47.869 5526-5554/rt_mjt.roomtest D/SA: ID=17 AT=X4 EI=Y4
    ID=18 AT=X4 EI=Y4
    ID=19 AT=X4 EI=Y4
    ID=20 AT=X5 EI=Y5
    ID=21 AT=X5 EI=Y5
    ID=22 AT=X5 EI=Y5
    ID=23 AT=X5 EI=Y5
    ID=24 AT=X6 EI=Y6
    ID=25 AT=X6 EI=Y6
    ID=26 AT=X6 EI=Y6
    ID=27 AT=X6 EI=Y6
06-17 02:56:47.870 5526-5554/rt_mjt.roomtest D/SA: ID=28 AT=X7 EI=Y7
    ID=29 AT=X7 EI=Y7
    ID=30 AT=X7 EI=Y7
    ID=31 AT=X7 EI=Y7
    ID=32 AT=X8 EI=Y8
    ID=33 AT=X8 EI=Y8
    ID=34 AT=X8 EI=Y8
    ID=35 AT=X8 EI=Y8
    ID=36 AT=X9 EI=Y9
    ID=37 AT=X9 EI=Y9
    ID=38 AT=X9 EI=Y9
    ID=39 AT=X9 EI=Y9
  • 注意:由于多个线程在没有控制/排序的情况下运行,结果可能会很奇怪

使用的addSomeData方法是:-

private void addSomeData() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            SequenceAction sa = new SequenceAction();
            for (int i=0; i < 10; i++) {
                sa.setSequenceId(0);
                sa.setActionType("X" + String.valueOf(i));
                sa.setExtraInfo("Y" + String.valueOf(i));
                mTestDB.SequenceActionDaoAccess().insertSingleRow(sa);
            }
        }
    }) .start();
}

“我相信你必须在房间之前进去……”您的意思是在实例化文件室数据库之前执行清除运行索引的SQL吗?-戈什

不一定,但在Room打开数据库之前,也就是在您尝试对其进行任何操作之前。添加了调用代码(在Overidden activities onStart()方法中),之后立即调用对addSomeData的某个房间Db访问米凯特

下面是一个在RoomDatabase被实例化后,但在它被用来访问/打开数据库之前调用resetSequextAction方法的例子:-

@Override
protected void onStart() {
    super.onStart();
    mTestDB = Room.databaseBuilder(this,TestDatabase.class,TestDatabase.DBNAME).build(); //<<<< Room DB instantiated
    resetSequenceAction(); //<<<< reset the sequence (adding trigger if needed)
    addSomeData(); // This will be the first access open
    addSomeData();
    addSomeData();
    addSomeData();
    listAllRows();
 类似资料:
  • 我正在从事一个项目,该项目在少数几个服务器上管理许多数据库,我们正在使用liquibase来保持我们的数据库结构是最新的。 根据我的理解,liquibase的目的是在第一次识别到为特定数据库执行的变更日志文件时生成databasechangelogLock和databasechangelogLock表。 谢谢

  • null (如果可能的话,它应该在db中创建表并生成html文件。我看到了http://velocity.apache.org/,所以是否可以使用它生成java代码。)

  • 我刚刚开始Spring Boot并设法在PostgreSQL数据库中创建表。然后,我将一个配置文件添加到我的项目中,该文件将把数据插入数据库。但是,据我所见,代码并没有命中这个配置文件并重新创建表。我试图更改文件中的参数,但这没有任何意义,结果,在运行空记录的每个应用程序上重新创建表(在Java控制台just create table上也可以看到)。那么,我错过了什么? 应用程序.属性: 学生配置

  • 启动Application ationContext时出错。要显示自动配置报告,请在启用调试的情况下重新运行应用程序 当我尝试运行我的Spring Boot应用程序时,我收到了上面的消息。 有人知道我如何在启用“调试”的情况下重新运行应用程序吗? 我正在Intellij(2016.1.2版)中运行应用程序 我的跑步课如下所示, 为了回应Darren下面的回答,我修改了properties.yml文

  • 我有一个带有cron作业spring boot项目。作业只应运行一次。它应该再次触发,只有当应用程序崩溃或应用程序重新启动。对此有什么cron表达式吗?或者有什么办法可以实现这一点?

  • 当我的树莓电源故障时,Android的东西在主页上重启,而没有我的应用程序,我必须手动重新发送它,我如何修复Android的东西在重启时自动运行应用程序?