java 为什么密钥不在SQL Room中生成?

6qqygrtg  于 2023-04-28  发布在  Java
关注(0)|答案(1)|浏览(83)

用户代码:

@Entity
public class User {
    @PrimaryKey(autoGenerate = true)
    public Integer uid;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}

道:

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
        "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User user);

    @Delete
    void delete(User user);
}

活动:

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

        DataBase db = Room.databaseBuilder(getApplicationContext(), DataBase.class, "DB")
                .allowMainThreadQueries().build();

        User user = new User();
        user.firstName = "Ste";
        user.lastName = "Kor";

        UserDao userDao = db.userDao();
        userDao.insertAll(user);
    }
}

正如我从文档中了解到的,@PrimaryKey(autoGenerate = true)应该为uid生成一个新的标识符。但是当我添加一个新对象时,它被分配了ID 0,应用程序崩溃了。
如果你手动设置这个值,那么一切正常,但是我需要它自己设置

vuktfyat

vuktfyat1#

如果使用Integer,则Room期望uid为null以生成值。如果使用了一个值,比如0,那么该值将用于uid。
由于insertAll方法没有指定onConflict策略,因此默认策略是ABORT。因此,使用0,多次将导致由于UNIQUE约束冲突而崩溃(作为主键的值必须是唯一的)。

*IGNORE的onConflict策略(e.例如@Insert(onConflict = OnConflictStrategy.IGNORE))不会导致崩溃,但不会插入该行。
*REPLACE的onConflict策略(e.例如@Insert(onConflict = OnConflictStrategy.REPLACE))将替换已经存在的行,方法是删除该行并插入另一行(与更新该行相反)

  • 知道已经做了什么通常是有用。@Insert函数可以返回一个long/Long值。
  • 如果插入了一行,则值将为rowid(对于整数类型主键,该列是特殊的、通常隐藏的rowid列的别名)
  • 如果一行尚未插入(即e.忽略),则该值将为**-1**(注意,尽管实际上可以将-1指定为值)

正如我从文档中了解到的,@PrimaryKey(autoGenerate = true)应该为uid生成一个新的标识符。
本文件具有误导性/虚假性。如果你使用autoGenerate false或者默认的@PrimaryKey,那么当指定一个空的uid时,仍然会生成uid。
但是,有一个细微的区别,即当autoGenerate为true时,SQLite**AUTOINCREMENT**关键字包含在表定义中。

AUTOINCREMENT(* autoGenerate = true *)关键字添加了一个约束(规则),即生成的值必须大于任何使用的值(即使具有最高值的行已被删除)。SQLite需要一种方法来记录使用过的最高值,它通过使用一个名为sqlite_sequence的系统表来做到这一点,如果需要,SQLite将创建该表。
sqlite_sequence表中的每个表都有一行具有AUTOINCREMENT,并且这是存储值的位置。访问和维护这个额外的表需要开销。事实上,SQLite文档说 * The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed. *

我建议最有效/正确的选择是用途:

@Entity
public class User {
    @PrimaryKey
    public Long uid;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}
  • 只要Integer不具有保存64位有符号值的能力,Long就可以。

并在插入新行时始终确保uid为空。

演示

也许考虑一下下面的内容,它展示了所述的大部分内容。注意,autoGenerate保留为默认值false(建议在大多数情况下更好)
因此,上面的User类一直在使用。
但是,UserDao界面已经扩展,以满足使用3个讨论/提到的冲突策略(ABORT,IGNORE和REPLACE):

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
            "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    Long insertAll(User user);
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insertAllWithReplace(User user);
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insertAllWithIgnore(User user);

    @Delete
    void delete(User user);
}

为了方便使用上面的@Database注解抽象类TheDatabase:-

@Database(entities = {User.class}, exportSchema = false, version = 1)
abstract class TheDatabase extends RoomDatabase {
    abstract UserDao getUserDao();

    private static volatile TheDatabase instance;
    static TheDatabase getInstance(Context context) {
        if (instance==null) {
            instance= Room.databaseBuilder(context,TheDatabase.class,"the_database.db")
                    .allowMainThreadQueries()
                    .build();
        }
        return instance;
    }
}

最后,一些活动代码演示了各种插入函数:

  • 将数据库中的当前数据写入日志logAllUsers
  • 插入行,指定冲突策略并捕获任何失败,并将插入的结果(返回的rowid值)写入日志。
    主要活动:-
public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    UserDao dao;

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

        /* Prepare to use the database and daos */
        db = TheDatabase.getInstance(this);
        dao = db.getUserDao();

        /* Prepare the 1 User that will be used throughout */
        User newUser = new User();
        newUser.firstName="Fred";
        newUser.lastName="Bloggs";

        /* Stage 1 insert with generated uid and ABORT conflict strategy */
        dao.insertAll(newUser);
        logAllUsers("_STAGE1");

        /* AS uid is null then the following will add 3 new rows */
        for (int i = 0; i < 3; i++ ) {
            dao.insertAll(newUser);
        }
        logAllUsers("_STAGE2");

        /* SHOULD NOT REALLY BE USED as the uid value will be 0*/
        newUser.uid=0;
        dao.insertAll(newUser);
        logAllUsers("_STAGE3");

        /* More in-depth debugging of inserts */
        /* duplicate use of 0 uid with ABORT */
        logAttemptedInsert(newUser,"_STAGE4", OnConflictStrategy.ABORT);
        /* duplicate use of 0 uid with REPLACE */
        logAttemptedInsert(newUser,"_STAGE5", OnConflictStrategy.REPLACE);
        /* duplicate use of 0 uid with IGNORE */
        logAttemptedInsert(newUser,"_STAGE6", OnConflictStrategy.IGNORE);
        /* Negative uid  ~~~ BEST TO NOT USE ~~~ */
        newUser.uid = -100;
        logAttemptedInsert(newUser,"_STAGE_7",OnConflictStrategy.IGNORE);
    }

    void logAllUsers(String tagSuffix) {
        for(User u: dao.getAll()) {
            Log.d("DBINFO" + tagSuffix,"User ID is " + u.uid + " FirstName is " + u.firstName + " LastName is " + u.lastName);
        }
    }

    void logAttemptedInsert(User user, String tagSuffix, Integer onConflictStrategy) {
        long insertedRowid= -888;
        try {
            switch(onConflictStrategy) {
                case OnConflictStrategy.IGNORE:
                    insertedRowid = dao.insertAllWithIgnore(user);
                    break;
                case OnConflictStrategy.REPLACE:
                    insertedRowid = dao.insertAllWithReplace(user);
                    break;
                default:
                    insertedRowid = dao.insertAll(user);
                    break;
            }
        } catch (SQLiteException e) {
            Log.d("DBINFO_" + tagSuffix,"SQLITE EXCEPTION TRAPPED. Exception Message=\n\t" + e.getMessage());
            insertedRowid = -999;
        } finally {
            Log.d("DBINFO" + tagSuffix,"INSERT RESULT for USER WITH ID as " + user.uid + " FIRSTNAME as " + user.firstName + " and LASTNAME as " + user.lastName + " WAS " + insertedRowid);
            logAllUsers(tagSuffix);
        }
    }
}
  • 请注意,上面的设计只运行一次。
    演示运行结果

当运行到日志的输出包括:-

2023-04-27 11:02:00.108 D/DBINFO_STAGE1: User ID is 1 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.133 D/DBINFO_STAGE2: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.134 D/DBINFO_STAGE2: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.134 D/DBINFO_STAGE2: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.134 D/DBINFO_STAGE2: User ID is 4 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.141 D/DBINFO_STAGE3: User ID is 0 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.141 D/DBINFO_STAGE3: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.141 D/DBINFO_STAGE3: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.141 D/DBINFO_STAGE3: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.141 D/DBINFO_STAGE3: User ID is 4 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.144 D/DBINFO__STAGE4: SQLITE EXCEPTION TRAPPED. Exception Message=
        UNIQUE constraint failed: User.uid (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
2023-04-27 11:02:00.144 D/DBINFO_STAGE4: INSERT RESULT for USER WITH ID as 0 FIRSTNAME as Fred and LASTNAME as Bloggs WAS -999
2023-04-27 11:02:00.145 D/DBINFO_STAGE4: User ID is 0 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.145 D/DBINFO_STAGE4: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.145 D/DBINFO_STAGE4: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.145 D/DBINFO_STAGE4: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.146 D/DBINFO_STAGE4: User ID is 4 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.148 D/DBINFO_STAGE5: INSERT RESULT for USER WITH ID as 0 FIRSTNAME as Fred and LASTNAME as Bloggs WAS 0
2023-04-27 11:02:00.150 D/DBINFO_STAGE5: User ID is 0 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.150 D/DBINFO_STAGE5: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.151 D/DBINFO_STAGE5: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.151 D/DBINFO_STAGE5: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.151 D/DBINFO_STAGE5: User ID is 4 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.153 D/DBINFO_STAGE6: INSERT RESULT for USER WITH ID as 0 FIRSTNAME as Fred and LASTNAME as Bloggs WAS -1
2023-04-27 11:02:00.155 D/DBINFO_STAGE6: User ID is 0 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.155 D/DBINFO_STAGE6: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.156 D/DBINFO_STAGE6: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.156 D/DBINFO_STAGE6: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.156 D/DBINFO_STAGE6: User ID is 4 FirstName is Fred LastName is Bloggs

2023-04-27 11:02:00.157 D/DBINFO_STAGE_7: INSERT RESULT for USER WITH ID as -100 FIRSTNAME as Fred and LASTNAME as Bloggs WAS -100
2023-04-27 11:02:00.160 D/DBINFO_STAGE_7: User ID is -100 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.161 D/DBINFO_STAGE_7: User ID is 0 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.161 D/DBINFO_STAGE_7: User ID is 1 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.161 D/DBINFO_STAGE_7: User ID is 2 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.161 D/DBINFO_STAGE_7: User ID is 3 FirstName is Fred LastName is Bloggs
2023-04-27 11:02:00.161 D/DBINFO_STAGE_7: User ID is 4 FirstName is Fred LastName is Bloggs
  • 阶段1已经插入了单行,并且如所解释/预期的,已经生成了具有值1的uid。
  • 如所解释/预期的,阶段2已经插入了具有生成的uid值(包括2-4)的另外3行。
  • 阶段3已经插入了具有特定uid值0的另一行,如所解释/预期的。
  • 阶段4没有插入另一个以0作为指定uid值的行,ABORT的默认onConflict策略导致捕获并报告的异常。4个现有行(0-4包括)是唯一存在的行,因为返回代码-999以指示捕获的异常。
  • 阶段5具有REPLACED行0,返回0作为插入的rowid(注意不是-1表示没有插入任何内容)。同样,所有5行都存在。
  • 阶段6已忽略插入,因为uid为0的行已存在。与阶段4不同,没有底层异常,因此返回-1,通常表示未插入任何内容。只有五个行存在。
  • 阶段6插入了一个负值为-100的行,并且是从插入返回的值(如果使用-1,那么根据返回的-1,您将不知道是否插入了该行)。现在有6行。App检测

使用应用程序检查,然后显示最终结果,以确认6行的最终结果,如下所示:

使用应用程序检查的新查询,可以看到sqlite_master的缺失,如下所示:-

  • 如果使用autoGenerate true,则资源昂贵的sqlite_sequence表可以被视为如下:

  • slqite_sequence本身将包含:-

  • 即UID(4)的最高使用值。

要显示这个隐藏的别名rowid,则:-x1c4d 1x

备注

  • 在Room中,仅通过注解支持rowid的表。但是,SQLite确实支持***WITHOUT ROWID表***,在这种情况下不能使用AUTOINCREMENT

  • AUTOINCREMENT又名autoGenerate=true,由于生成的行必须大于任何现有行的规则,如果生成的值允许的最大值(9223372036854775807),当没有AUTOINCREMENT时,算法将尝试使用未使用的值(如果使用了负的rowid,则这可以是负值),因此不太可能导致SQLITE_FULL异常。

  • 对于当前的存储设备,不可能有这样的行数。但是,如果9223372036854775807被指定为一个值,即使只有几行,那么由于规则,如果使用AUTOINCREMENT,则会发生异常。

  • 例如:-

  • 然后:-

  • 事件日志显示 11:48 am Database Inspector:运行语句时出错:数据库或磁盘已满(代码13 SQLITE_FULL),即使只有7行

  • 如果没有AUTOINCREMENT,则使用相同的2个查询会导致:

  • 可以看出,具有New作为first_name和ASIF作为姓氏的第8行已经插入了可用的未使用的生成值,而不是崩溃。

相关问题