android - 关系房间数据库 : The class must be either entity or database view

标签 android kotlin android-room

我想了解如何将 Room 与关系表一起使用。我创建了一个 Job 模型,它有一个位置列表,因此需要 Job 和 Location 对象之间的一对多关系。为此,我创建了一个 JobWrapper 数据类来保存作业和位置。但是,在构建时出现以下错误:

The class must be either @Entity or @DatabaseView. - java.util.Collectionerror: Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.Collection \models\JobWrapper.java:12: error: Cannot find the child entity column parentId in java.util.Collection. Options: private java.util.Collection<models.Location> locations; public final class JobWrapper { ^ Tried the following constructors but they failed to match:
JobWrapper(models.Job,java.util.Collection<models.Location>) -> [param:job -> matched field:job, param:locations -> matched field:unmatched] models\JobWrapper.java:9: error: Cannot find setter for field.

我注意到它至少找不到位置表。但是,我不知道如何处理这个问题。从数据库读取时没有出现问题 - 它第一次出现是在我尝试使用 JobDAO 将数据放入数据库时​​出现的。我已经花了一天时间尝试解决它,因此正在寻找解决方案或关于如何解决它的一些建议。

注意:我一直遵循以下指南:

  1. https://developer.android.com/training/data-storage/room/relationships#one-to-many
  2. https://dev.to/normanaspx/android-room-how-works-one-to-many-relationship-example-5ad0

以下是我项目中的一些相关代码 fragment :

JobWrapper.kt

data class JobWrapper(
    @Embedded val job: Job,

    @Relation(
        parentColumn = "jobid",
        entityColumn = "parentId"
    ) var locations : Collection<Location>
)

工作

@Entity
data class Job (
    @PrimaryKey
    @NonNull
    var jobid : String,

    @NonNull
    @ColumnInfo(name = "job_status")
    var status : JobStatus,

    @NonNull
    @SerializedName("createdByAuth0Id")
    var creator : String,

    @SerializedName("note")
    var note : String?,

    @NonNull
    var organisationId : String,

    @NonNull
    var type : JobType,

    @SerializedName("atCustomerId")
    @NonNull
    @ColumnInfo(name = "working_at_customer_id")
    var workingAtCustomerId : String,

    @SerializedName("toCustomerId")
    @NonNull
    @ColumnInfo(name = "working_to_customer_id")
    var workingToCustomerId : String,
)

JobStatus.kt

enum class JobStatus {
    CREATED,
    READY,
    IN_PROGRESS,
    FINISHED
}

Location.kt

@Entity
data class Location (
    @PrimaryKey(autoGenerate = true)
    var entityId: Long,

    @NonNull
    var parentId: String,

    @NonNull
    var locationId: String,

    @NonNull
    var type: String
) {
    constructor() : this(0, "", "", "")
}

JobDao.kt

@Dao
interface JobDAO {
    @Transaction
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(job: JobWrapper)

    @Transaction
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(jobs: List<JobWrapper>)

    @Transaction
    @Update
    fun update(job: JobWrapper)

    @Transaction
    @Delete
    fun delete(job: JobWrapper)

    @Transaction
    @Query("DELETE FROM Job")
    fun deleteAll()

    @Transaction
    @Query("SELECT * FROM Job")
    fun getAll(): LiveData<List<JobWrapper>>
}

最佳答案

正如 Kraigolas 所指出的,您只能直接使用 JobWrapper 来提取您需要通过实际的底层实体插入/删除/更新的数据。

考虑以下问题

  • (与 Kraigolas 的解决方案不同,扩展功能在 JobDao 中而不是在存储库中(swings 和 roundabouts 争论哪个更好))

  • 测试注意事项为了简洁和方便,我做了一些更改,因此您必须修改以适应。

JobDao

@Dao
interface JobDAO {

    /* Core/Underlying DAO's */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(job: Job): Long
    @Insert()
    fun insert(location: List<Location>): List<Long>
    @Transaction
    @Delete
    fun delete(location: List<Location>)
    @Delete
    fun delete(job: Job)
    @Update
    fun update(job: Job)
    @Update
    fun update(location: List<Location>)

    @Transaction
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(job: JobWrapper): Long {
        val rv =insert(job.job)
        insert(job.locations)
        return rv
    }

    @Transaction
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(jobs: List<JobWrapper>) {
        for (jw: JobWrapper in jobs) {
            insert(jw)
        }
    }

    @Transaction
    @Update
    fun update(job: JobWrapper) {
        update(job.locations)
        update(job.job)
    }

    /* Delete according to JobWrapper allowing optional deletion of locations */
    @Transaction
    @Delete
    fun delete(job: JobWrapper, deleteChildLocations: Boolean) {
        if (deleteChildLocations) {
            delete(job.locations)
        }
        delete(job.job)
    }

    /* will orphan locations as is */
    /* Note using Foreign Keys in Location (to Job) with ON DELETE CASCADE */
    @Transaction
    @Query("DELETE FROM Job")
    fun deleteAll()

    @Transaction
    @Query("SELECT * FROM Job")
    fun getAll(): List<JobWrapper>

    @Transaction
    @Query("SELECT * FROM job WHERE jobid = :jobid")
    fun getJobWrapperByJobId(jobid: String ): JobWrapper
}
  • 可以看出,Core Dao 包括 Job 和 Location 操作
  • JobWrapper 操作调用核心操作
  • 为方便起见,我使用了列表而不是集合(也就是我只涉足 Kotlin)
  • 为方便起见,类型已更改为使用 String 而不是 JobType

正如我测试过的,使用的演示/示例如下(显然 JobDao 与上面相同)

使用的 POJO 和实体是/曾经是:-

作业包装器

 data class JobWrapper(
    @Embedded val job: Job,

    @Relation(
        parentColumn = "jobid",
        entityColumn = "parentId",
        entity = Location::class
    ) var locations : List<Location>
)
  • 列表而不是集合

工作

@Entity
data class Job (
    @PrimaryKey
    @NonNull
    var jobid : String,

    @NonNull
    @ColumnInfo(name = "job_status")
    var status : String,

    @NonNull
    var creator : String,

    var note : String?,

    @NonNull
    var organisationId : String,

    @NonNull
    var type : String,

    @NonNull
    @ColumnInfo(name = "working_at_customer_id")
    var workingAtCustomerId : String,

    @NonNull
    @ColumnInfo(name = "working_to_customer_id")
    var workingToCustomerId : String,
)
  • 大致相同,但为方便起见,使用字符串而不是对象

位置

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Job::class,
            parentColumns = ["jobid"],
            childColumns = ["parentId"],
            onUpdate = ForeignKey.CASCADE,
            onDelete = ForeignKey.CASCADE
        )
    ])
data class Location (
    @PrimaryKey(autoGenerate = true)
    var entityId: Long,

    @NonNull
    var parentId: String,

    @NonNull
    var locationId: String,

    @NonNull
    var type: String
) {
    constructor() : this(0, "", "", "")
}
  • 为引用完整性添加外键以及 CASCADE 删除(除非您更改 jobid,否则实际上不需要 UPDATE CASCADE,其他更新不会级联(也不需要))

@Database 使用了 JobDatabase

@Database(entities = [Location::class,Job::class],version = 1)
abstract class JobDatabase: RoomDatabase() {
    abstract fun getJobDao(): JobDAO
    
    companion object {
        var instance: JobDatabase? = null
        fun getInstance(context: Context): JobDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(context, JobDatabase::class.java, "job.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as JobDatabase
        }
    }
}
  • allowMainThreadQueries 用于允许在主线程上进行测试

测试/演示 Activity MainActivity

class MainActivity : AppCompatActivity() {

    lateinit var db: JobDatabase
    lateinit var dao: JobDAO
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = JobDatabase.getInstance(this)
        dao = db.getJobDao()

        var jobId: String = "Job1"
        var jw = JobWrapper(
            Job(
                jobId,
                "x",
                "Fred",
                "Note for Job1",
                "Org1",
                "A","Cust1",
                "Cust1"),
            listOf(
                Location(0,jobId,"loc1","J"),
                Location(0,jobId,"Loc2","K"),
                Location(0,jobId,"Loc3","L")
            )
        )
        dao.insert(jw)
        dao.insertAll(createJobWrapperList(10))
        dao.delete(dao.getJobWrapperByJobId("job6"),true)
        var jobWrapper = dao.getJobWrapperByJobId("job7")
        jobWrapper.job.creator = "update creator"
        for (l in jobWrapper.locations) {
            if (l.type == "M") l.type= "UPDATED"
        }
        dao.update(jobWrapper)
    }

    fun createJobWrapperList(numberToCreate: Int): List<JobWrapper> {
        val l = mutableListOf<JobWrapper>()
        for(i in 1..numberToCreate) {
            var jid = "job$i"
            l.add(
                JobWrapper(
                Job(jid,"X","Creator$i","Note for $jid","org$jid","T","custA","custT"),
                    arrayListOf(
                        Location(0,jid,"loc_$jid.1","L"),
                        Location(0,jid,"loc_$jid.2","M"),
                        Location(0,jid,"loc_$jid.3","N")
                    )
                )
            )
        }
        return l.toList()
    }
}

这个:-

  1. 获取数据库实例和 dao。
  2. 通过单个 JobWrapper 添加工作及其位置
  3. 通过 createJobWrapperList 函数生成的 JobWrappers 列表添加 x (10) 个工作和每个工作的 3 个位置。 4.删除通过 getJobWrapperByJobId 获取的 JobWrapper,包括使用 delete 删除底层位置 (true) 与 jobid“job6”关联的 JobWrapper .
  4. 获取与“job7”关联的 JobWrapper 更改创建者并将类型为“M”的位置更改为“UPDATED”(仅此一个),然后使用 update (JobWrapper) 应用更新。

警告

使用 JobWrapper 插入,因为它具有 REPLACE 冲突策略,如果它替换,将导致额外的位置,因为将始终生成 entityId。

结果

运行上面的结果:-

工作表:-

enter image description here

  • 可以看到 job6 已被删除(添加 11 行,剩下 10 行)并且 job7 的创建者已更新。

位置表:-

enter image description here

  • 可以看出没有 job6 位置(之前是 33 个位置 (11 * 3),现在是 30 个)并且类型 M 的位置已根据传递的 JobWrapper 进行了更新。

你问:-

How do I ensure the relationship to the right parent (job) when inserting the childs (locations)?

我认为以上说明了方法。

关于android - 关系房间数据库 : The class must be either entity or database view,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67843988/

相关文章:

c# - 如何修复 Xamarin.Forms android 应用程序中的 Java.Lang.OutOfMemoryError?

php - Proguard 和 Gson - 转换 arraylist 内的变量名称

kotlin - 如何在 Kotlin 中初始化大的不可变映射?

java - Travis 在 Bintray 中部署库。构建文件夹不存在

java - 无法覆盖 Kotlin 中的 Java 函数

android - 具有多种 View 类型的RecyclerView和具有边界回调的分页库

android - 是否可以在 Room Dao 中向上转换?

Android - 如何取消选择 ActionBar 中的所有选项卡

android - 在 Room 中编写基于某个枚举值进行选择的类型安全查询

Android - 如果应用程序未运行,则 Pushbots NullpointerException