【问题标题】:Room database with one-to-one relation like Address, City and State具有一对一关系的房间数据库,如地址、城市和州
【发布时间】:2021-12-31 11:55:06
【问题描述】:

我在 android 文档中查找了我的问题的答案,但我找不到。要使用这些类中包含的信息创建 recyclerview,如何在 Room 中获取此信息的列表

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = City::class,
            parentColumns = arrayOf("id"),
            childColumns = arrayOf("cityfk"),
            onDelete = ForeignKey.NO_ACTION
        )
    ]
)
data class Address(
    @PrimaryKey
    @ColumnInfo
    var id: Long = 0
) : Serializable {

    @ColumnInfo
    var name: String = ""

    @ColumnInfo(index = true)
    var cityfk: Long = 0

}

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = State::class,
            parentColumns = arrayOf("id"),
            childColumns = arrayOf("statefk"),
            onDelete = ForeignKey.NO_ACTION
        )
    ]
)
data class City(
    @PrimaryKey
    @ColumnInfo
    var id: Long = 0
) : Serializable {

    @ColumnInfo
    var name: String = ""

    @ColumnInfo(index = true)
    var statefk: Long = 0
}

@Entity
data class State(
    @PrimaryKey
    @ColumnInfo
    var id: Long = 0
) : Serializable {

    @ColumnInfo
    var name: String = ""

}

如何获得列出类的地址列表?

如何在 ANSI SQL 中得到这样的结果:

select     ADDRESS.NAME ADDRESS
         , CITY.NAME CITY
         , STATE.NAME STATE

from       ADDRESS

join       CITY
on         CITY.ID = ADDRES.CITYFK

join       STATE
on         STATE.ID = CITY.STATEFK

【问题讨论】:

    标签: android database sqlite android-room-relation android-room-embedded


    【解决方案1】:

    您通常会有一个 POJO 来表示组合数据。然后,您可以为提取的列设置一个字段/变量,注意值与喜欢的命名变量匹配。

    您可以使用@Embedded 将实体包含在其整体中,因此理论上嵌入地址城市和州。

    • 查看变量/列名问题

    您可以将@Embedded 与@Relation 一起用于子代(子代),但不能用于孙代(例如州)。您需要一个带有 State POJO 的基础 City,其中 City 是嵌入的,State 通过 @Relation 关联。

    • 当使用 @Relation 作为房间构建来自父级的基础查询时,变量/列名不是问题。

    变量/列名问题

    Room 根据变量名称将列映射到变量。因此,如果对所有三个实体使用更简单的 @Embedded,idname 列将会出现问题。

    • 我建议始终使用唯一的名称,例如addressId,cityId,StateId,(至少对于列名,例如@ColumnInfo(name = "addressId"))但更简单的是只有 var addressid。

    • 另一种方法是在一些上使用@Embedded(prefix = "the_prefix"),这告诉空间将变量与带有前缀的列名匹配,因此您需要在SQL中使用AS。显然 the_prefix 将被更改以适应。

    道之

    如果将@Embedded 与@Relation 一起使用,那么您只需要获取父级

    @Query("SELECT * FROM address")
    fun getAddressWithCityAndWithState(): List<AddressWithCityAndWithState>
    
    • 其中 AddressWithCityAndWithState 是具有地址 @Embedded 和具有 @Relation 的 CityWithState 的 POJO。

    您还需要带有 City @Embedded 的 CityWithState POJO 和带有 @Relation 的 State。

    如果使用前缀为“city_”的城市和前缀为“state_”的州嵌入地址、城市和州,那么您将使用类似:-

    @Query("SELECT address.*, city.id AS city_id, city.name AS city_name, state.id AS state_id, state.name AS state_name FROM address JOIN city ON address.cityfk = city.it JOIN state ON city.statefk = state.id")
    fun getAddressWithCityAndWithState(): List<AddressWithCityAndWithState>
    
    • 其中 AddressWithCityAndWithState 是具有地址、城市和州@Embedded 的 POJO

    注意以上是原则性的。

    工作示例

    以下是一个基于

    的工作示例
    • a) 重命名列以避免歧义和
    • b) 在 POJO AddressWithCityWithState 中使用所有三个类的 @Embedded

    首先更改地址、城市和州以重命名列:-

    地址:-

    @Entity(
        foreignKeys = [
            ForeignKey(
                entity = City::class,
                parentColumns = arrayOf("city_id"), //<<<<<<<<<< CHANGED
                childColumns = arrayOf("cityfk"),
                onDelete = ForeignKey.NO_ACTION
            )
        ]
    )
    data class Address(
        @PrimaryKey
        @ColumnInfo(name ="address_id") //<<<<<<<<<< ADDED name
        var id: Long = 0
    ) : Serializable {
    
        @ColumnInfo(name = "address_name") //<<<<<<<<<< ADDDED name
        var name: String = ""
    
        @ColumnInfo(index = true)
        var cityfk: Long = 0
    }
    

    城市:-

    @Entity(
        foreignKeys = [
            ForeignKey(
                entity = State::class,
                parentColumns = arrayOf("state_id"), //<<<<<<<<<< changed
                childColumns = arrayOf("statefk"),
                onDelete = ForeignKey.NO_ACTION
            )
        ]
    )
    data class City(
        @PrimaryKey
        @ColumnInfo(name = "city_id") // <<<<<<<<<< ADDED name
        var id: Long = 0
    ) : Serializable {
    
        @ColumnInfo(name = "city_name") //<<<<<<<<<< ADDED name
        var name: String = ""
    
        @ColumnInfo(index = true)
        var statefk: Long = 0
    }
    

    状态:-

    @Entity
    data class State(
        @PrimaryKey
        @ColumnInfo(name = "state_id") // ADDED name
        var id: Long = 0
    ) : Serializable {
    
        @ColumnInfo(name = "state_name") // ADDED name
        var name: String = ""
    }
    

    接下来是 POJO AddressWithCityWithState :-

    data class AddressWithCityWithState (
        @Embedded
        val address: Address,
        @Embedded
        val city: City,
        @Embedded
        val state: State
    )
    
    • 由于列名唯一,不需要prefix = ?

    一个合适的DAO:-

    @Query("SELECT * FROM address JOIN city on address.cityfk = city.city_id JOIN state ON city.statefk = state.state_id")
        fun getAllAddressesWithCityAndWithState(): List<AddressWithCityWithState>
    
    • 由于列重命名而简化,因此 * 代替 AS 子句用于不明确的列名

    使用上面的:-

        allDao = db.getAllDao()
    
        var state = State()
        state.name = "State1"
        var stateid = allDao.insert(state)
        var city = City()
        city.name = "City1"
        city.statefk = stateid
        var cityid = allDao.insert(city)
        var address = Address()
        address.name = "Address1"
        address.cityfk = cityid
        allDao.insert(address)
    
        for(awcws: AddressWithCityWithState in allDao.getAllAddressesWithCityAndWithState()) {
            Log.d("DBINFO","${awcws.address.name}, ${awcws.city.name}, ${awcws.state.name}")
        }
    

    日志中的结果是:-

    2021-11-22 07:43:28.574 D/DBINFO: Address1, City1, State1
    

    其他工作示例(不更改列名)

    没有对实体(地址、城市和州)进行任何更改。以下是其他选项的工作示例。

    1- 将完整地址作为单个字符串获取,所需的只是查询,例如:-

    @Query("SELECT address.name||','||city.name||','||state.name AS fullAddress FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id ")
    fun getAddressesAsStrings(): List<String>
    
    • 当然,下拉选择器没多大用处,因为您无法确定数据库中行的来源。

    2 - 具有明确列名的基本 POJO

    POJO:-

    data class AddressWithCityWithState(
        var address_id: Long,
        var address_name: String,
        var city_id: Long,
        var city_name: String,
        var state_id: Long,
        var state_name: String
    )
    

    查询:-

    /*
    * Returns multiple columns renamed using AS clause to disambiguate
    * requires POJO with matching column names
    * */
    @Query("SELECT " +
            "address.id AS address_id, address.name AS address_name, " +
            "city.id AS city_id, city.name AS city_name, " +
            "state.id AS state_id, state.name AS state_name " +
            "FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id")
    fun getAddressesWithCityAndStateViaBasicPOJO(): List<AddressWithCityWithState>
    

    3- 使用 EMBEDS 的 POJO

    POJO:-

    data class AddressWithCityWithStateViaEmbeds(
        @Embedded
        var address: Address,
        @Embedded(prefix = cityPrefix)
        var city: City,
        @Embedded(prefix = statePrefix)
        var state: State
    )  {
        companion object {
            const val cityPrefix = "city_"
            const val statePrefix = "state_"
        }
    }
    

    查询:-

    /*
    *   Returns multiple columns renamed according to the prefix=? coded in the
    *   @Embedded annotation
    *
     */
    @Query("SELECT address.*, " +
            "city.id AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "id," +
            "city.name AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "name," +
            "city.statefk AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "statefk," +
            "state.id AS " + AddressWithCityWithStateViaEmbeds.statePrefix + "id," +
            "state.name AS " + AddressWithCityWithStateViaEmbeds.statePrefix + "name " +
            "FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id")
    fun getAddressesWithCityAndStateViaEmbedPOJO(): List<AddressWithCityWithStateViaEmbeds>
    

    4- 带有父 EMBED 和子 RELATE 的 POJO

    POJO 的:-

    data class CityWithState(
        @Embedded
        var city: City,
        @Relation(
            entity = State::class,
            parentColumn = "statefk",
            entityColumn = "id"
        )
        var state: State
    )
    

    和:-

    data class AddressWithCityWithStateViaRelations(
        @Embedded
        var address: Address,
        @Relation(
            entity = City::class, /* NOTE NOT CityWithState which isn't an Entity */
            parentColumn = "cityfk",
            entityColumn = "id"
        )
        var cityWithState: CityWithState
    )
    

    和查询:-

    @Transaction
    @Query("SELECT * FROM address")
    fun getAddressesWithCityAndStateViaRelations(): List<AddressWithCityWithStateViaRelations>
    
    • 注意@Tranaction 的使用,因此由 Room 构建的底层查询都在单个数据库事务中完成。

    将上述内容投入使用

    活动中的以下代码使用全部 4 来输出相同的结果:-

    class MainActivity : AppCompatActivity() {
    
        lateinit var db: TheDatabase
        lateinit var dao: AllDao
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val TAG: String = "DBINFO"
    
            db = TheDatabase.getInstance(this)
            dao = db.getAllDao()
    
            var state = State(1)
            state.name = "State1"
            val state1Id = dao.insert(state)
            state.id = 2
            state.name = "State2"
            val state2Id = dao.insert(state)
    
            var city = City(10)
            city.name = "City1"
            city.statefk = state1Id
            val city1Id = dao.insert(city)
            city.id = 11
            city.name = "City2"
            city.statefk = state2Id
            val city2Id = dao.insert(city)
            city.id = 12
            city.name = "City3"
            city.statefk = state1Id
            val city3Id = dao.insert(city)
    
            var address = Address(100)
            address.name = "Address1"
            address.cityfk = city1Id
            dao.insert(address)
            address.id = address.id + 1
            address.name = "Address2"
            address.cityfk = city2Id
            dao.insert(address)
            address.id = address.id + 1
            address.name = "Address3"
            address.cityfk = city3Id
    
            for (s: String in dao.getAddressesAsStrings()) {
                Log.d(TAG + "STRG", s)
            }
            for (awcws: AddressWithCityWithState in dao.getAddressesWithCityAndStateViaBasicPOJO()) {
                Log.d(TAG + "BASICPOJO", "${awcws.address_name}, ${awcws.city_name}, ${awcws.state_name}")
            }
            for (awcwsve: AddressWithCityWithStateViaEmbeds in dao.getAddressesWithCityAndStateViaEmbedPOJO()) {
                Log.d(TAG + "EMBEDS","${awcwsve.address.name}, ${awcwsve.city.name}, ${awcwsve.state.name}")
            }
            for(awcwsvr: AddressWithCityWithStateViaRelations in dao.getAddressesWithCityAndStateViaRelations()) {
                Log.d(TAG + "MIXED","${awcwsvr.address.name}, ${awcwsvr.cityWithState.city.name}, ${awcwsvr.cityWithState.state.name}")
            }
        }
    }
    

    日志的输出是:-

    2021-11-22 12:33:54.322 D/DBINFOSTRG: Address1,City1,State1
    2021-11-22 12:33:54.322 D/DBINFOSTRG: Address2,City2,State2
    
    2021-11-22 12:33:54.324 D/DBINFOBASICPOJO: Address1, City1, State1
    2021-11-22 12:33:54.324 D/DBINFOBASICPOJO: Address2, City2, State2
    
    2021-11-22 12:33:54.326 D/DBINFOEMBEDS: Address1, City1, State1
    2021-11-22 12:33:54.326 D/DBINFOEMBEDS: Address2, City2, State2
    
    2021-11-22 12:33:54.332 D/DBINFOMIXED: Address1, City1, State1
    2021-11-22 12:33:54.332 D/DBINFOMIXED: Address2, City2, State2
    

    【讨论】:

    • 感谢@MikeT 的详细回复和示例。这是一个很大的帮助。
    • @Rodrigo 很好。您不妨考虑勾选答案。
    • 已经完成了! ;)
    • @Rodrigo,这是一个赞成票(非常感谢),赞成/反对票下方有一个灰色的勾号,如果它是好的/有帮助的/最好的,您可以接受它。
    猜你喜欢
    • 2018-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    • 2020-03-10
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    相关资源
    最近更新 更多