kotlin 关系房间数据库:该类必须是实体或数据库视图

ars1skjm  于 2023-03-24  发布在  Kotlin
关注(0)|答案(2)|浏览(84)

我试图了解如何使用房间与关系表.我已经创建了一个作业模型,它有一个位置列表,因此需要一个1到多的作业和位置对象之间的关系.为此,我已经创建了一个JobWrapper数据类来容纳作业和位置.但是,当构建我得到以下错误:
类必须是@Entity或@DatabaseView. - java.util.Collectionerror:实体和POJO必须有一个可用的公共构造函数。您可以有一个空的构造函数,也可以有一个参数与字段匹配的构造函数(按名称和类型)。- java.util.Collection \models\JobWrapper.java:12:错误:在java. util. Collection中找不到子实体列parentId。选项:private java.util.Collection<models.Location>locations; public final class JobWrapper { ^尝试了以下构造函数,但它们无法匹配:
JobWrapper(models.Job,java.util.Collection<models.Location>)-〉[param:job -〉matched field:job,param:locations -〉matched field:unmatched] models\JobWrapper.java:9:错误:找不到字段的setter。
我注意到它至少找不到locations表。但是,我不知道如何处理这个问题。这个问题在从数据库阅读时没有出现-它第一次出现是在我试图用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
    下面是我的项目中的一些相关代码片段:

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
}

位置.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>>
}
oewdyzsn

oewdyzsn1#

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

  • (与Kraigolas的解决方案不同,扩展的函数在JobDao中,而不是在存储库中(关于哪个更好的争论))
  • 注意测试我已经做了一些变化,为简洁和方便,所以你将不得不修改,以适应。

[***] 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
}
  • 可以看出,核心Dao包括Job和Location操作
  • JobWrapper操作调用Core操作
  • 列表已被用来代替收藏为我的方便(又名我只涉足Kotlin)
  • 为了方便起见,类型已更改为使用String,而不是JobType

我已经测试过了,使用的演示/示例如下(显然JobDao是如上所述)
所使用的POJO和实体为:

JobWrapper

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,
)
  • 基本相同,但为了方便,使用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删除添加外键(除非更改作业ID,否则不需要UPDATE CASCADE,其他更新不会级联(也不需要))

@使用的数据库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用于允许在主线程上进行测试

测试/演示活动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.获取DB示例和dao。
1.通过单个JobWrapper添加作业及其位置
1.通过由createJobWrapperList函数生成的作业 Package 列表添加x(10)个作业和每个作业的3个位置。4.删除通过getJobWrapperByJobId获得的作业 Package ,包括使用与作业ID“job 6”相关联的作业 Package 的delete删除底层位置(true)。
1.获取与“job 7”关联的JobWrapper,更改创建者并将类型为“M”的位置更改为“UPDATED”(仅一个),然后使用update(JobWrapper)应用更新。

警告

使用JobWrapper进行插入,因为它具有REPLACE冲突策略,如果它总是生成entityId,则会导致额外的位置。

结果

运行上述结果:-

职务表:-

  • 可以看出,job 6已被删除(添加了11行,还剩10行),job 7的创建者已被更新。
    位置表格:-

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

你问:
在插入查尔兹(位置)时,如何确保与正确的父项(作业)的关系?
我认为上面的例子说明了如何。

mrfwxfqh

mrfwxfqh2#

JobWrapper没有关联的表。注意,它很适合从数据库中拉取,因为它会从Location表 * 和Job表 * 中抓取,但是用JobWrapper插入数据库是没有意义的,因为没有关联的表。
相反,您需要分别插入Job s和Location s。数据库可以一起查询它们,因为它们是由jobidparentid相关的,所以您不必担心它们之间的关系丢失,并且您仍然可以查询您的JobWrapper
如果给出了我上面的解释,你仍然认为你应该能够插入一个JobWrapper,那么你可以用这样的方法创建一个仓库:

suspend fun insertWrapper(wrapper : JobWrapper){
    dao.insertJob(wrapper.job)
    dao.insertLocations(wrapper.locations)
}

其中insertJob插入单个JobinsertLocations插入Location的列表。

相关问题