我本以为那是行不通的。首先是因为主键永远不应该为空,其次是因为如果没有 autoGenerate id 会如何表现?
房间实际使用null
,将 0 转换为null
如果 autogenerate 为 true,则在 INTEGER PRIMARY KEY 的特殊情况下,SQLite 会生成一个值null
已指定。
- 由于 SQLite 要求 INTEGER PRIMARY KEY 为整数,因此它处理 null,在这种特殊情况下,生成整数值。这可以解释为
rowid 表的 PRIMARY KEY 约束(只要它不是真正的主键或整数主键)实际上与 UNIQUE 约束是一样的。因为它不是真正的主键,所以 PRIMARY KEY 的列允许为 NULL,这违反了所有 SQL 标准。
- 加粗了我认为推断 INTEGER PRIMARY KEY 不能为空的异常。
- from https://www.sqlite.org/rowidtable.html https://www.sqlite.org/rowidtable.html
如果 autogenerate 为 true,则生成的代码(根据/来自下面的演示)包括:-
"INSERT OR IGNORE INTO `AutoGenTrueTable` (`id`,`name`) VALUES (nullif(?, 0),?)"
虽然在 autogenerate 为 false 的情况下,生成的代码改为使用:-
"INSERT OR IGNORE INTO `AutoGenFalseTable` (`id`,`name`) VALUES (?,?)"
-
请注意,OR IGNORE
是因为onConflictStrategy的IGNORE
.
-
后一个示例,即 autogenerate 为 false,因此如果 id 字段为 0,则将使用值 0
-
生成的java可以通过Android Studio的Android View轻松找到。 DAO 位于与接口/抽象类同名但后缀为 的类中_Impl
.
-
Room 使用的通过 Android SQLite API 的绑定将 null 转换为null
关键字(令牌)。
- 与名称相同的类
@Database
带注释的类,但带有_Impl
后缀,还会有其他有用的代码,例如在createAllTables
可以找到创建表的 SQL 的方法。
他们不是都有相同的id吗?
NO因为主键是隐式唯一的,因此如果 id 是主键并且无论自动生成是 true 或 false,则 id 永远不会与同一个表中的另一个 id 相同。
If autogenerate
is true那么 Room 还将 0 转换为未提供的值,因此 0 会产生生成的值。
However如果您指定值 0 且如果自动生成为假 (默认显式或隐式)那么 0 将用于 id,不允许多次使用,但可以由onConflictStrategy
的插入物。
The DEMO下面说明了上面的内容(请注意,使用了 IGNORE onConflictStrategy,因此错误的重复 0 id 将被忽略).
关于整数主键的一些知识 (e.g. @PrimaryKey val whatever:Int
或者更正确地说Long
) 又名的别名rowid column.
- 可以使用 Byte、Short 等,因为它们是整数类型,但它们的用途有限。
- Long 更正确,因为该值可以与 64 位有符号整数一样大(
Int
不够大,Long
是的,在很多情况下这不是问题)。
如果某列专门为 INTEGER(Room 在编译时确定)并且 是主键(在列或表级别),则该列是特殊的通常隐藏列的别名,rowid,所有表都有(除了WITHOUT ROWID 表,Room 不通过注释支持)。
-
请注意,rowid列始终存在(除非该表是一个WITHOUT ROWID表,这也是Room不支持的)并且总是分配一个整数值(无论是否指定或暗示INTEGER PRIMARY KEY)。
-
虽然rowid用于 SQLite 接受其他别名参见https://www.sqlite.org/rowidtable.html https://www.sqlite.org/rowidtable.html了解更多rowids
此类列必须是 INTEGER 类型值(除了 rowid 列或其别名之外,任何列类型实际上都可以存储任何类型的值,尽管 Room 不支持这一点)。此外,如果在插入时没有为此类列指定值,则该值将由 SQLite 生成。这通常比最高值大 1rowid对于那张桌子。
因此,只要情况是没有为该列提供值,就会生成该值(并且很可能比最高值大 1)。
如果房间的autogenerate=true
然后使用添加 SQLiteAUTOINCREMNET
表定义/模式的关键字。这改变了该值的生成方式,因为它是两个值中较大的一个,其中一个是最高的rowid在表中,另一个是最高记录/曾经使用过的rowidvalue,如果具有最高 rowid 的行已被删除,则该值可能高于最高 rowid。
- 请注意,这假设 sqlite_sequence 表除了 SQLite 对表的处理之外没有更改(它可以被操纵但要小心)
简而言之AUTOINCREMENT
添加一个约束/规则,表示生成的值必须大于任何使用的值。然而,这要求最高的分配值必须存储在其他地方。 SQLite 将此附加值存储在名为的表中sqlite_序列每个表有 1 行。获取和维护这样的值会产生开销,因此 SQLite 文档指出:-
-
AUTOINCRMENT 关键字会带来额外的 CPU、内存、磁盘空间和磁盘 I/O 开销,如果不是严格需要,则应避免使用。通常不需要它。 see https://www.sqlite.org/autoinc.html https://www.sqlite.org/autoinc.html
使用 null 或 0 与autogenerate=true
, Room 不提供值,因此生成值。如果提供任何其他值,则使用该值(如果行已存在,这将导致唯一冲突,独特的冲突处理onConFlictStrategy
相应注释的参数('@Insert' 或@Update
) 插入或更新时)
- 如果使用 INSERT 或 UPDATE 查询,则实际的 SQLite OR ??????可以指定冲突操作,例如“插入或忽略......”
如前所述,如果没有 autogenerate=true,0将用作值(请参见下面的演示),因此为了避免自动增量的开销/浪费/低效率,那么该字段应该可以为空,并且使用 null 来生成值。
- Java 中,基元(int、long)的默认值为 0 并且不能为 null,现在/过去有点不同,并且有一些问题。
DEMO
也许考虑下面的演示,其中使用了 3 个表(实体),其中一个使用了 autogenerate=true,另外 2 个表(实体)未指定,因此隐含了 autogenerate=false。其他两者之间的区别在于,第一个不允许 id 为空且默认 id 为 0,第二个允许且 id 默认值为 null。
The 3 @Entity
带注释的类是:-
@Entity
data class AutoGenTrueTable(
@PrimaryKey(autoGenerate = true)
val id: Long=0,
val name: String
)
@Entity
data class AutoGenFalseTable(
@PrimaryKey
val id: Long=0,
val name: String
)
@Entity
data class AutoGenFalseNullableTable(
@PrimaryKey
val id: Long?=null,
val name: String
)
为了演示 sqlite_sequence (从中提取所有数据),然后是一个 POJO:-
data class SQLiteSequence(
val name: String,
val seq: Long
)
an @Dao
带注释的界面(适合插入、专门删除和提取数据):-
@Dao
interface AllDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(autoGenTrueTable: AutoGenTrueTable): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(autoGenFalseTable: AutoGenFalseTable): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(autoGenFalseNullableTable: AutoGenFalseNullableTable): Long
@Query("DELETE FROM autogentruetable WHERE id >=:highAsOrHigherThanId")
fun deleteFromAGTTByHighIds(highAsOrHigherThanId: Long)
@Query("DELETE FROM autogenfalsetable WHERE id >=:highAsOrHigherThanId")
fun deleteFromAGFTByHighIds(highAsOrHigherThanId: Long)
@Query("DELETE FROM autogenfalsenullabletable WHERE id >=:highAsOrHigherThanId")
fun deleteFromFalseNullableByHighIds(highAsOrHigherThanId: Long)
@Query("SELECT * FROM autogentruetable")
fun getAllFromAutoGenTrue(): List<AutoGenTrueTable>
@Query("SELECT * FROM autogenfalsetable")
fun getAllFromAutoGenFalse(): List<AutoGenFalseTable>
@Query("SELECT * FROM autogenfalsenullabletable")
fun getAllFromAutoGenFalseNullable(): List<AutoGenFalseNullableTable>
@Query("SELECT * FROM sqlite_sequence")
fun getAllFromSQLiteSequence(): List<SQLiteSequence>
}
一个相当直截了当的@Database
带注释的抽象类,允许使用主线程以简化演示:-
@Database(entities = [AutoGenTrueTable::class,AutoGenFalseTable::class,AutoGenFalseNullableTable::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAOs(): AllDAOs
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, TheDatabase::class.java, "the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
最后是一些插入/提取和删除数据的活动代码,对于所有 3 个演示表,将提取的数据(包括 sqlite_master 的内容)写入各个阶段的日志中:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDAOs()
var stage = 0
logEverything(stage++)
for (i: Int in 1..3) {
dao.insert(AutoGenTrueTable(0,"AG_TT_ZERO_${i}"))
//dao.insert(AutoGenTrueTable(null,"AG_TT_NULL_${i}")) not nullable cannot be used
dao.insert(AutoGenTrueTable(name = "AG_TT_DEFAULT_${i}"))
dao.insert(AutoGenTrueTable(id = 100,"AG_TT_100_${i}"))
dao.insert(AutoGenFalseTable(0,"AG_FT_ZERO_${i}"))
//dao.insert(AutoGenFalseTable(id = null,name = "AG_FT_NULL_${i}")) not nullable cannot be used
dao.insert(AutoGenFalseTable(name = "AG_FT_DEFAULT_${i}"))
dao.insert(AutoGenFalseTable(id = 100, "AG_FT_100_${i}"))
dao.insert((AutoGenFalseNullableTable(0, "AG_FTNULL_ZERO_${i}") ))
dao.insert((AutoGenFalseNullableTable(null, "AG_FTNULL_NULL_${i}") ))
dao.insert((AutoGenFalseNullableTable( name = "AG_FTNULL_DEFAULT_${i}") ))
dao.insert(AutoGenFalseNullableTable(id = 100, name = "AG_FTNULL_100_${i}"))
}
logEverything(stage++)
dao.deleteFromAGTTByHighIds(100)
dao.deleteFromAGFTByHighIds(100)
dao.deleteFromFalseNullableByHighIds(100)
logEverything(stage++)
for (i: Int in 1..3) {
dao.insert(AutoGenTrueTable(0,"AG_TT_ZERO_${i}"))
//dao.insert(AutoGenTrueTable(null,"AG_TT_NULL_${i}")) not nullable cannot be used
dao.insert(AutoGenTrueTable(name = "AG_TT_DEFAULT_${i}"))
dao.insert(AutoGenTrueTable(id = 100,"AG_TT_100_${i}"))
dao.insert(AutoGenFalseTable(0,"AG_FT_ZERO_${i}"))
//dao.insert(AutoGenFalseTable(id = null,name = "AG_FT_NULL_${i}")) not nullable cannot be used
dao.insert(AutoGenFalseTable(name = "AG_FT_DEFAULT_${i}"))
dao.insert(AutoGenFalseTable(id = 100, "AG_FT_100_${i}"))
dao.insert((AutoGenFalseNullableTable(0, "AG_FTNULL_ZERO_${i}") ))
dao.insert((AutoGenFalseNullableTable(null, "AG_FTNULL_NULL_${i}") ))
dao.insert((AutoGenFalseNullableTable( name = "AG_FTNULL_DEFAULT_${i}") ))
dao.insert(AutoGenFalseNullableTable(id = 100, name = "AG_FTNULL_100_${i}"))
}
logEverything(stage++)
}
fun logEverything(stage: Int) {
Log.d("DBINFO_STARTSTAGE_${stage}","Starting logging of stage ${stage}")
logAllFromAGTT(stage)
logAllFromAGFT(stage)
logAllFromAGFTN(stage)
logAllFromSQLite_Sequence(stage)
}
fun logAllFromAGTT(stage: Int) {
for(a in dao.getAllFromAutoGenTrue()) {
Log.d("DBINFO_AGTT_STG${stage}","ID is ${a.id} NAME is ${a.name}")
}
}
fun logAllFromAGFT(stage: Int) {
for(a in dao.getAllFromAutoGenFalse()) {
Log.d("DBINFO_AGFT_STG${stage}","ID is ${a.id} NAME is ${a.name}")
}
}
fun logAllFromAGFTN(stage: Int) {
for(a in dao.getAllFromAutoGenFalseNullable()) {
Log.d("DBINFO_AGFTN_STG${stage}","ID is ${a.id} NAME is ${a.name}")
}
}
fun logAllFromSQLite_Sequence(stage: Int) {
for(ss in dao.getAllFromSQLiteSequence()) {
Log.d("DBINFO_SSEQ_STG${stage}","TABLE IS ${ss.name} HIGHEST ID STORED FOR THE TABLE IS ${ss.seq}")
}
}
}
第一次运行应用程序安装时,输出为(阶段之间有 2 个空行,3 个表之间有一个空行):-
2023-04-14 12:01:26.073 D/DBINFO_STARTSTAGE_0: Starting logging of stage 0
2023-04-14 12:01:26.244 D/DBINFO_STARTSTAGE_1: Starting logging of stage 1
2023-04-14 12:01:26.246 D/DBINFO_AGTT_STG1: ID is 1 NAME is AG_TT_ZERO_1
2023-04-14 12:01:26.246 D/DBINFO_AGTT_STG1: ID is 2 NAME is AG_TT_DEFAULT_1
2023-04-14 12:01:26.246 D/DBINFO_AGTT_STG1: ID is 100 NAME is AG_TT_100_1
2023-04-14 12:01:26.246 D/DBINFO_AGTT_STG1: ID is 101 NAME is AG_TT_ZERO_2
2023-04-14 12:01:26.246 D/DBINFO_AGTT_STG1: ID is 102 NAME is AG_TT_DEFAULT_2
2023-04-14 12:01:26.247 D/DBINFO_AGTT_STG1: ID is 103 NAME is AG_TT_ZERO_3
2023-04-14 12:01:26.247 D/DBINFO_AGTT_STG1: ID is 104 NAME is AG_TT_DEFAULT_3
2023-04-14 12:01:26.249 D/DBINFO_AGFT_STG1: ID is 0 NAME is AG_FT_ZERO_1
2023-04-14 12:01:26.249 D/DBINFO_AGFT_STG1: ID is 100 NAME is AG_FT_100_1
2023-04-14 12:01:26.250 D/DBINFO_AGFTN_STG1: ID is 0 NAME is AG_FTNULL_ZERO_1
2023-04-14 12:01:26.250 D/DBINFO_AGFTN_STG1: ID is 1 NAME is AG_FTNULL_NULL_1
2023-04-14 12:01:26.250 D/DBINFO_AGFTN_STG1: ID is 2 NAME is AG_FTNULL_DEFAULT_1
2023-04-14 12:01:26.250 D/DBINFO_AGFTN_STG1: ID is 100 NAME is AG_FTNULL_100_1
2023-04-14 12:01:26.251 D/DBINFO_AGFTN_STG1: ID is 101 NAME is AG_FTNULL_NULL_2
2023-04-14 12:01:26.251 D/DBINFO_AGFTN_STG1: ID is 102 NAME is AG_FTNULL_DEFAULT_2
2023-04-14 12:01:26.251 D/DBINFO_AGFTN_STG1: ID is 103 NAME is AG_FTNULL_NULL_3
2023-04-14 12:01:26.251 D/DBINFO_AGFTN_STG1: ID is 104 NAME is AG_FTNULL_DEFAULT_3
2023-04-14 12:01:26.253 D/DBINFO_SSEQ_STG1: TABLE IS AutoGenTrueTable HIGHEST ID STORED FOR THE TABLE IS 104
2023-04-14 12:01:26.258 D/DBINFO_STARTSTAGE_2: Starting logging of stage 2
2023-04-14 12:01:26.261 D/DBINFO_AGTT_STG2: ID is 1 NAME is AG_TT_ZERO_1
2023-04-14 12:01:26.261 D/DBINFO_AGTT_STG2: ID is 2 NAME is AG_TT_DEFAULT_1
2023-04-14 12:01:26.262 D/DBINFO_AGFT_STG2: ID is 0 NAME is AG_FT_ZERO_1
2023-04-14 12:01:26.263 D/DBINFO_AGFTN_STG2: ID is 0 NAME is AG_FTNULL_ZERO_1
2023-04-14 12:01:26.263 D/DBINFO_AGFTN_STG2: ID is 1 NAME is AG_FTNULL_NULL_1
2023-04-14 12:01:26.263 D/DBINFO_AGFTN_STG2: ID is 2 NAME is AG_FTNULL_DEFAULT_1
2023-04-14 12:01:26.264 D/DBINFO_SSEQ_STG2: TABLE IS AutoGenTrueTable HIGHEST ID STORED FOR THE TABLE IS 104
2023-04-14 12:01:26.333 D/DBINFO_STARTSTAGE_3: Starting logging of stage 3
2023-04-14 12:01:26.336 D/DBINFO_AGTT_STG3: ID is 1 NAME is AG_TT_ZERO_1
2023-04-14 12:01:26.336 D/DBINFO_AGTT_STG3: ID is 2 NAME is AG_TT_DEFAULT_1
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 100 NAME is AG_TT_100_1
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 105 NAME is AG_TT_ZERO_1
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 106 NAME is AG_TT_DEFAULT_1
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 107 NAME is AG_TT_ZERO_2
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 108 NAME is AG_TT_DEFAULT_2
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 109 NAME is AG_TT_ZERO_3
2023-04-14 12:01:26.337 D/DBINFO_AGTT_STG3: ID is 110 NAME is AG_TT_DEFAULT_3
2023-04-14 12:01:26.340 D/DBINFO_AGFT_STG3: ID is 0 NAME is AG_FT_ZERO_1
2023-04-14 12:01:26.340 D/DBINFO_AGFT_STG3: ID is 100 NAME is AG_FT_100_1
2023-04-14 12:01:26.342 D/DBINFO_AGFTN_STG3: ID is 0 NAME is AG_FTNULL_ZERO_1
2023-04-14 12:01:26.342 D/DBINFO_AGFTN_STG3: ID is 1 NAME is AG_FTNULL_NULL_1
2023-04-14 12:01:26.342 D/DBINFO_AGFTN_STG3: ID is 2 NAME is AG_FTNULL_DEFAULT_1
2023-04-14 12:01:26.342 D/DBINFO_AGFTN_STG3: ID is 3 NAME is AG_FTNULL_NULL_1
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 4 NAME is AG_FTNULL_DEFAULT_1
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 100 NAME is AG_FTNULL_100_1
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 101 NAME is AG_FTNULL_NULL_2
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 102 NAME is AG_FTNULL_DEFAULT_2
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 103 NAME is AG_FTNULL_NULL_3
2023-04-14 12:01:26.343 D/DBINFO_AGFTN_STG3: ID is 104 NAME is AG_FTNULL_DEFAULT_3
2023-04-14 12:01:26.346 D/DBINFO_SSEQ_STG3: TABLE IS AutoGenTrueTable HIGHEST ID STORED FOR THE TABLE IS 110
结果解释(一点)
可以清楚地看到,id 不重复(它们不能重复,因为在所有情况下主键都是隐式唯一的),而且 sqlite_sequence 只记录 TT 表中曾经使用过的最高 id(autogenerate=true 的 id)。
不太容易看出的是,当 autogenerate 为 false 时,至少暗示/默认,多次使用任何值(包括 0)将不会生成 id,即 Room 将该值传递给插入。该演示有忽略onConflictStrategy
因此,这些尝试的重复操作将被忽略,不会发生任何故障。
因此,FT 和 FTNULL 的 ???_ZERO_nn 的 id 为 0,并且只有 nn 为 1 的单行。与 TT_ZERO 不同,TT_ZERO 中所有 3 行都已插入,生成的 ID 不为 0。