首页 > 解决方案 > 获取主键而不插入空行

问题描述

所以在我的应用程序中,当用户单击添加某些东西时,我应该创建一个实体 A来承载用户提供的值,这个实体 A有一个自动增量主键,在构造实体 A 的过程中还有另一个携带实体 A的键作为外键及其组合键的一部分的实体。

所以我的问题是该房间阻止我创建其他实体,而无需在其构造函数中提供实体 A的键对其进行注释,@NonNull因为它是其复合键的一部分并且不能为空。

现在我不知道如何解决这个问题,

请告诉我您对此的看法,因为这是我第一次使用嵌入在应用程序中的数据库,所以我不知道应该如何处理它。

标签: androidsqliteandroid-roomandroid-architecture-components

解决方案


这个实体 A 有一个自动递增的主键

AUTOINCREMENT,在 RoomautoGenerate = true作为@PrimaryKey注释的一部分,实际上不会导致自动生成。相反,它是一个约束规则,它强制下一个自动生成的 rowid 大于任何存在或已经存在的(对于该表)。

如果列是 INTEGER PRIMARY KEY(或通过诸如 PRIMARY KEY 之类的列的表级定义隐含),则没有 AUTOINCREMENT,则该列将成为始终存在的 rowid 的别名(除了很少使用的 WITHOUT ROWID 表(无法做到所以在 Room via entity 中,这样的表没有注释))

rowid 始终是唯一的,并且始终自动生成,并且通常会更大(通常大于 1)。只有在 AUTOINCREMENT 发挥作用时达到最大值(9223372036854775807th rowid)时(除非故意操纵)。在这种情况下,使用 AUTOINCREMENT 你会得到一个 SQLITE_FULL 异常,没有 SQLITE 将尝试找到一个较低的未使用/空闲 rowid。

  • 由于不必要的开销, 个人从不使用 autoGenerate = true。
  • AUTOINCREMENT 的作用是有一个系统表 sqlite_sequence,每个表都有一行,它有 AUTOINCREMENT,它存储/维护表的最高分配 rowid。使用 AUTOINCREMENT 然后它使用 sqlite_sequence 值和最高 rowid 值中的较高者,然后加 1(没有它只使用最高 rowid 并加 1)。

从一开始就将我的实体作为我的应用程序中的自定义类使用是一个错误,我应该将实体与自定义类分开吗?

不需要有单独的类,实体可以用作独立类,房间注释被忽略。

每当用户单击添加选项时,我是否应该只推/插入一个空实体/行/元组以获取自动生成的密钥,以便我可以一路创建实体?

获取生成的密钥非常容易,并且单个插入的 @Insert 返回密钥(id),因此 @Dao@Insert abstract fun(entityA: EntityA): Long (long in Java)返回密钥,如果插入没有插入行,则返回 -1。

如果您使用@Insert 的列表/可变参数,那么它会返回一个Long 数组,每个元素返回插入的键(id)或-1。

因此,考虑到我认为您的问题,请考虑以下 3 个实体(如果 Java 则使用 Long 而不是 long 作为键,因为原语不能为空)。

@Entity
data class EntityA(
    @PrimaryKey
    var entityAKey: Long? = null,
    var otherAdata: String
)
  • 没有通过 autoGenerate = true 的 AUTOINCREMENT。
  • 没有@NOTNULL 注释

然后 :-

@Entity
data class EntityB(
    @PrimaryKey
    var entityBKey: Long?= null,
    var otherBdata: String
)

和 :-

@Entity(
    primaryKeys = ["entityBRef","entityARef","otherPartOfPrimaryKey"]
)
data class EntityC(
    var entityBRef: Long,
    var entityARef: Long,
    var otherPartOfPrimaryKey: Long,
    var otherCData: String
)

添加一些道:-

@Insert
abstract fun insert(entityA: EntityA): Long
@Insert
abstract fun insert(entityB: EntityB): Long
@Insert
abstract fun insert(entityC: EntityC): Long
  • 注意 Long 返回值(如果 Int 总是 Long 不编译)和生成的密钥应该总是很长,因为它们可能超过 Int 可以容纳的值。

最后考虑:-

    db = TheDatabase.getInstance(this)
    dao = db.getDao()
    
    var myfirstA = EntityA(otherAdata = "First")
    var myfirstB = EntityB(otherBdata = "The first B")
    var a1 = dao.insert(myfirstA)
    var b1 = dao.insert(myfirstB)
    dao.insert(EntityC(b1,a1,100L,"The first C using id's from the first A and the first B"))
  • 通过在主线程上运行allowMainThreadQueries()

和数据库: -

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

你甚至可以这样做:-

    dao.insert(EntityC(
        dao.insert(EntityB(otherBdata = "Second B")), 
        dao.insert(EntityA(otherAdata = "A's second")), 
        200,
        "blah")
    )
  • 显然,这可能用途有限,因为您需要预先知道这些值。

结果是:-

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

  • 通过 Android Studio 的 App Inspector(以前称为 Database Inspector)获得的数据库快照。

你也可以做/使用:-

    var my3rdA = EntityA(otherAdata = "3rd")
    my3rdA.entityAKey = dao.insert(my3rdA)

当然,当您从数据库中提取时,该对象将包含键 (id)(除非您故意选择不这样做)。


推荐阅读