Android Room 加入的返回类型

Posted

技术标签:

【中文标题】Android Room 加入的返回类型【英文标题】:Return type for Android Room joins 【发布时间】:2017-12-17 00:21:52 【问题描述】:

假设我想在两个实体 FooBar 之间做一个 INNER JOIN

@Query("SELECT * FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();

是否可以强制这样的返回类型?

public class FooAndBar 
    Foo foo;
    Bar bar;

当我尝试这样做时,我收到此错误:

error: Cannot figure out how to read this field from a cursor.

我也尝试过给表名加上别名以匹配字段名,但这也不起作用。

如果这是不可能的,我应该如何干净地构造一个兼容的返回类型,包括两个实体的所有字段?

【问题讨论】:

【参考方案1】:

是否可以强制这样的返回类型?

您可以在foobar 上尝试@Embedded 注释。这将告诉 Room 尝试从您的查询中获取列并将它们倒入 foobar 实例中。我只对实体进行了尝试,但文档表明它也应该与 POJO 一起使用。

但是,如果您的两个表具有相同名称的列,这可能无法正常工作。

【讨论】:

是的,这不起作用,因为我的实体有同名的列(如id)... @pqvst:“我应该如何干净地构造一个兼容的返回类型,包括两个实体的所有字段?” -- 要么选择foobar@Embedded 并将其余字段直接放入FooAndBar,或者将所有字段直接放入FooAndBar。在 SQL 中使用AS 重命名结果集中的重复列,并根据需要使用@ColumnInfo 将那些AS-renamed 列映射到您想要的字段。 这正是我想要避免做的事情......我觉得不是很“干净”:/ @pqvst:就目前而言,您的查询应该导致关于输出中重复列的 SQLite 错误,或者充其量是SQLiteDatabase 在幕后创建的Cursor 不会同时具有这两者id 列(以及任何其他重复的列)。您需要 SQL 查询中的所有列在结果集中具有不同的名称,否则 Cursor 将没有所有数据。解决此问题后,调整 FooBar 实体以匹配并使用 @Embedded 解决方案。 @pqvst: “我觉得不是很“干净””——然后摆脱 JOIN 并进行两次 DAO 调用,一个获取 Foo,另一个获取获取关联的Bar。 Room 的明确意图是任意查询导致任意 POJO 用于输出,就像您需要适当的 POJO 来进行改造调用一样。实体主要用于普通的 CRUD。【参考方案2】:

@Query("SELECT * FROM Foo")
List<FooAndBar> findAllFooAndBar();

班级FooAndBar

public class FooAndBar 
    @Embedded
    Foo foo;

    @Relation(parentColumn =  "Foo.bar_id", entityColumn = "Bar.id")
    List<Bar> bar;
    // If we are sure it returns only one entry
    // Bar bar;

    //Getter and setter...

这个解决方案似乎有效,但我并不为此感到自豪。 你怎么看?

编辑:另一种解决方案

Dao,我更喜欢明确选择,但“*”会完成这项工作:)

@Query("SELECT Foo.*, Bar.* FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();

班级FooAndBar

public class FooAndBar 
    @Embedded
    Foo foo;

    @Embedded
    Bar bar;

    //Getter and setter...

编辑:从2.2.0-alpha01版本开始,房间@Relation注解可以管理一对一关系

【讨论】:

如果FooBar 之间存在冲突,我相信您可以通过创建后一个类的子集表示来消除这些冲突,例如public class BareBar ...some bar fields... ,然后将entity = BareBar.class 添加到@Relation。见:developer.android.com/reference/android/arch/persistence/room/… 第二种解决方案导致“多个字段具有相同的columnName”编译错误然后实体具有名称相同的PK属性:id 第二种解决方案真的有效吗?因为我收到“无法弄清楚如何转换光标...”错误。另外我正在使用@Embedded(prefix = "foo_") & @Embedded(prefix = "bar_") @musooff 第二种解决方案仅在@Embeddeds 上没有设置prefix 值时对我有效。为了解决重复的列名,我必须为每个表上的每个类字段使用@ColumnInfo(name = ...) 此链接帮助developer.android.com/training/data-storage/room/…【参考方案3】:

另一种选择是编写一个新的 POJO 来表示您的 JOIN 查询的结果结构(甚至支持列重命名以避免冲突):

@Dao
public interface FooBarDao 
   @Query("SELECT foo.field1 AS unique1, bar.field1 AS unique2 "
          + "FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
   public List<FooBar> getFooBars();

   static class FooBar 
       public String unique1;
       public String unique2;
   
    

见:room/accessing-data.html#query-multiple-tables

【讨论】:

这适用于具有相同名称的字段,只需要为它们有一个别名。【参考方案4】:

试试这个方法。 例如,我在 ProductAttribute 之间有 M2M(多对多)关系。许多产品有许多属性,我需要通过Product.id获取所有属性并通过PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING排序记录。

|--------------|  |--------------|  |-----------------------|
| PRODUCT      |  | ATTRIBUTE    |  | PRODUCTS_ATTRIBUTES   |
|--------------|  |--------------|  |-----------------------|
| _ID:  Long   |  | _ID: Long    |  | _ID: Long             |
| NAME: Text   |  | NAME: Text   |  | _PRODUCT_ID: Long     |
|______________|  |______________|  | _ATTRIBUTE_ID: Long   |
                                    | DISPLAY_ORDERING: Int |
                                    |_______________________|

因此,模型将如下所示:

@Entity(
    tableName = "PRODUCTS",
    indices = [
        Index(value = arrayOf("NAME"), unique = true)
    ]
)
class Product 

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "NAME")
    @SerializedName(value = "NAME")
    var name: String = String()




@Entity(
    tableName = "ATTRIBUTES",
    indices = [
        Index(value = arrayOf("NAME"), unique = true)
    ]
)
class Attribute 

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "NAME")
    @SerializedName(value = "NAME")
    var name: String = String()


“加入”表将是:

@Entity(
    tableName = "PRODUCTS_ATTRIBUTES",
    indices = [
        Index(value = ["_PRODUCT_ID", "_ATTRIBUTE_ID"])
    ],
    foreignKeys = [
        ForeignKey(entity = Product::class, parentColumns = ["_ID"], childColumns = ["_PRODUCT_ID"]),
        ForeignKey(entity = Attribute::class, parentColumns = ["_ID"], childColumns = ["_ATTRIBUTE_ID"])
    ]
)
class ProductAttribute 

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "_PRODUCT_ID")
    var _productId: Long = 0

    @ColumnInfo(name = "_ATTRIBUTE_ID")
    var _attributeId: Long = 0

    @ColumnInfo(name = "DISPLAY_ORDERING")
    var displayOrdering: Int = 0


AttributeDAO 中,要获取基于Product._ID 的所有属性,您可以执行以下操作:

@Dao
interface AttributeDAO 

    @Query("SELECT ATTRIBUTES.* FROM ATTRIBUTES INNER JOIN PRODUCTS_ATTRIBUTES ON PRODUCTS_ATTRIBUTES._ATTRIBUTE_ID = ATTRIBUTES._ID INNER JOIN PRODUCTS ON PRODUCTS._ID = PRODUCTS_ATTRIBUTES._PRODUCT_ID WHERE PRODUCTS._ID = :productId ORDER BY PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING ASC")
    fun getAttributesByProductId(productId: Long): LiveData<List<Attribute>>


如果您有任何问题,请告诉我。

【讨论】:

当两个表中的数据都超过 1000 时,此查询很可能会导致应用程序冻结。您能否建议我在查询增长和返回结果增长时如何避免冻结应用程序@dphans @SureshMaidaragi 与分页库一起使用。现在将返回查询从LiveData&lt;List&lt;Attribute&gt;&gt; 更改为DataSource.Factory&lt;Int, Attribute&gt;。否则,使用查询中的LIMIT 页面大小。 你为什么用@SerializedName 我对 ManyToMany 和 RoomDB 有疑问:this one @CoolMind 抱歉不需要注释 (@SerializedName) :D【参考方案5】:

这是我的餐桌

@Entity(tableName = "_food_table")
data class Food(@PrimaryKey(autoGenerate = false)
            @ColumnInfo(name = "_food_id")
            var id: Int = 0,
            @ColumnInfo(name = "_name")
            var name: String? = "")

这是我的购物车表和模型类(食品车)

@Entity(tableName = "_client_cart_table")
data class CartItem(
                @PrimaryKey(autoGenerate = false)
                @ColumnInfo(name = "_food_id")
                var foodId: Int? = 0,
                @Embedded(prefix = "_food")
                var food: Food? = null,
                @ColumnInfo(name = "_branch_id")
                var branchId: Int = 0)

注意:这里我们在两个表中看到 _food_id 列。它会抛出编译时错误。从@Embedded doc,您必须使用前缀来区分它们。

道内

@Query("select * from _client_cart_table inner join _food_table on _client_cart_table._food_id = _food_table._food_id where _client_cart_table._branch_id = :branchId")
fun getCarts(branchId: Int) : LiveData<List<CartItem>>

这个查询会返回这样的数据

CartItem(foodId=5, food=Food(id=5, name=Black Coffee), branchId=1)

我已经在我的项目中做到了这一点。所以试试吧。快乐编码

【讨论】:

以上是关于Android Room 加入的返回类型的主要内容,如果未能解决你的问题,请参考以下文章

Android Room 不确定如何将光标转换为方法的返回类型问题

如何在 ROOM android 中修复“不确定如何将光标转换为此方法的返回类型”

Android 数据库 Room从入门到进阶

Android LiveData和Room:getValue返回NULL

Android:如何使 Kotlin 中所有对象列表的类型转换器(用于 Room)通用

Android Jetpack ROOM 的Dao返回LiveData<Bean>封装及Bean普通的区别