我正在学习如何使用Jetpack Compose创建笔记应用程序的教程。这是tutorial的链接。教程中有一点他创建了这个实体:
@Entity
data class Note(
val title: String,
val content: String,
val timestamp: Long,
val color: Int,
@PrimaryKey val id: Int? = null
)
请注意,他使用的是一个可空值作为主键,而不是(autoGenerate = true)
。
我认为这是行不通的。首先因为主键不应该为null,其次因为没有autoGenerate,id将如何表现?它们不会都有相同的id吗?
我在创建房间实体时经常看到的代码是这样的:
@PrimaryKey(autoGenerate = true) val id: Int = 0
@PrimaryKey
注解是如何工作的?为什么两种方式都有效?哪一种更值得推荐?
编辑:我想澄清一下,教程中使用的代码可以正确编译并保存到数据库中。
2条答案
按热度按时间xlpyo6sf1#
我认为这是行不通的,首先因为主键不应该为null,其次因为没有autoGenerate,id将如何表现?
Room实际上使用
null
,如果autogenerate为true,则将0转换为null
,因为SQLite在INTEGER PRIMARY KEY的特殊情况下,当指定null
时会生成一个值。rowid表的PRIMARY KEY约束(只要它不是真正的主键或INTEGER PRIMARY KEY)实际上与UNIQUE约束是一样的。因为它不是真正的主键,所以PRIMARY KEY的列允许为NULL,这违反了所有SQL标准。
如果autogenerate为true,则生成的代码(根据/从下面的演示)包括:
而在autogenerate为false的情况下,生成的代码使用:
OR IGNORE
是因为IGNORE
的onConflictStrategy。_Impl
的类中。null
关键字(令牌)。@Database
注解类同名但后缀为_Impl
的类将具有其他有用的代码,例如在createAllTables
方法中,可以找到用于创建表的SQL。他们不是都有相同的身份证吗?
NO作为主键是隐式唯一的,因此如果它是主键,并且不管自动生成的是true还是false,则同一个表中的id永远不会与另一个id相同。
如果**
autogenerate
为true**,则Room还将0转换为不提供值,因此0会导致生成值。但是如果你指定一个值0,如果autogenerate为false*(默认情况下显式或隐式)* 那么0将被用于id,这将不允许超过一次,但可以由插入的
onConflictStrategy
处理。下面的DEMO说明了上述 (注意,使用了IGNORE onConflictStrategy,因此忽略了错误的重复0 id)。
一点关于INTEGER PRIMARY KEY(例如
@PrimaryKey val whatever:Int
或更正确的Long
)的信息,也就是rowid列的别名。Int
不够大,Long
足够大,在许多情况下这不是问题)。如果一个列是INTEGER (Room在编译时确定) 并且是PRIMARY KEY(在列或表级别),那么该列是一个特殊的通常隐藏的列的别名,rowid,所有表都有(除了WITHOUT ROWID表,Room不支持通过注解)。
这样的列必须是INTEGER类型的值 (除了rowid列或其别名之外,任何列类型实际上都可以存储任何类型的值,尽管Room不支持这一点)。此外,如果在插入时没有为这样的列指定值,则该值将由SQLite生成。这通常比该表的最大rowid大1。
因此,只要情况是没有为列提供值,则将生成该值(并且很可能比最高值大1)。
如果使用Room的
autogenerate=true
,则会将SQLiteAUTOINCREMNET
关键字添加到表定义/模式中。这会更改值的生成方式,因为它是两个值中的较大值,一个是表中最高的rowid,另一个是记录/使用过的最高rowid值,可能高于最高的rowid,如果具有最高rowid的行已被删除。简而言之,
AUTOINCREMENT
添加了一个约束/规则,规定生成的值必须大于任何使用的值。然而,这要求最高分配值必须存储在其他地方。SQLite将此附加值存储在名为sqlite_sequence的表中,每个表将有1行。获取和维护这样的值会产生开销,因此SQLite文档声明:-使用null或0与
autogenerate=true
相同,Room不提供值,因此生成值。如果提供任何其他值,则使用该值(如果行已经存在,则会导致唯一冲突,插入或更新时,使用相应注解的onConFlictStrategy
参数('@ Insert'或@Update
)进行唯一冲突处理)如前所述,如果没有autogenerate=true,0将被用作值(参见下面的演示),因此,为了避免AUTOINCREMENT的开销/浪费/效率低下,该字段应该是可空的,并使用null来生成值。
*Java,其中primitives(int,long)的默认值为0并且不能为null,是/是有点不同,有一些陷阱。
DEMO
也许可以考虑下面的演示,其中使用了3个表(实体),其中一个使用了autogenerate=true,另外2个没有指定,因此autogenerate=false是隐含的。另外两个之间的区别是,第一个不允许id为null,默认id为0,第二个允许id的默认值为null。
3个
@Entity
注解类是:要演示sqlite_sequence(从中提取所有数据),请执行POJO:-
一个
@Dao
注解接口(用于插入、专门删除和提取数据):-一个非常直接的
@Database
注解的抽象类,允许使用主线程来简化演示:最后是一些插入/提取和删除数据的活动代码,对于所有3个演示表,在各个阶段将提取的数据(包括sqlite_master的内容)写入日志:
当第一次运行应用程序安装时,输出为(阶段之间的2个空行和3个表之间的空行):-
结果解释(一点)
可以清楚地看到,id不是重复的(它们不能在所有情况下都是隐式唯一的,因为主键是隐式唯一的),而且sqlite_sequence只记录TT表使用过的最高id(autogenerate=true的那个)。
不太容易看到的是,autogenerate为false的地方,至少是隐含的/默认的,使用任何值超过一次,包括0,将不会生成id,也就是说Room将该值传递给insert。该演示具有IGNORE
onConflictStrategy
,因此这些尝试的重复被忽略,不会发生失败。因此,对于FT和FTNULL,???_ZERO_nn的id为0,并且只有nn为1的单行。与TT_ZERO不同,TT_ZERO的所有3行都已插入,生成的ID不为0。
kyvafyod2#
默认情况下,autoGenerate for primary key为false。这意味着默认情况下不会生成主键的随机id。我们需要指定autoGenerate = true以获取自动生成的id。
在本例中,每次在表中创建新对象时,都必须显式提供主键,因为它被指定为可空。
更多参考:主键