Android Room连接的返回类型

68

假设我想在两个实体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.

我还尝试过将表名别名设置为字段名以匹配它们,但也没有起作用。

如果这不可能,那应该如何清晰地构建一个兼容的返回类型,包含两个实体的所有字段?

5个回答

74

@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版本以来,room @Relation注释可以管理一对一关系


1
如果FooBar之间存在冲突,我相信通过创建后者类的子集表示来消除这些冲突,例如 public class BareBar { ...some bar fields... },然后将 entity = BareBar.class 添加到 @Relation 中,可以解决这个问题。请参考:https://developer.android.com/reference/android/arch/persistence/room/Relation.html - charles-allen
20
第二个解决方案会导致“多个字段具有相同的columnName”编译错误,当实体拥有同名的PK属性:“id”。 - Vinigas
3
第二种解决方案真的有效吗?因为我遇到了“无法找出如何转换光标…”的错误。此外,我正在使用@Embedded(prefix = "foo_")@Embedded(prefix = "bar_") - musooff
1
这个链接很有帮助:https://developer.android.com/training/data-storage/room/relationships#one-to-many - Clocker
第一个选项是否适用于LiveData或Observable?我更喜欢第二个选项,但由于重复的ID名称而无法使用。 - pavelperc
显示剩余5条评论

18

另一个选择是编写一个新的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


2
这在字段名称相同时有效,只需要为它们设置别名即可。 - live-love

13
尝试这种方法。 例如,我在产品属性之间有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()

}

“join”表将如下所示:

@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 - Suresh Maidaragi
1
@SureshMaidaragi 使用分页库。现在将返回查询从“LiveData<List<Attribute>>”更改为“DataSource.Factory<Int,Attribute>”。另一种方式是使用查询中的“LIMIT”页面大小。 - dphans
你为什么要使用 @SerializedName - CoolMind
我有一个关于ManyToMany和RoomDB的问题:这个 - SebasSBM
@CoolMind 抱歉,这里不需要注解(@SerializedName) :D - dphans
@dphans,好的,你可以编辑答案。 :) - CoolMind

3
有可能像这样强制返回类型吗?您可以尝试在foo和bar上注释@Embedded。这会告诉Room尝试将查询中的列取出并倒入foo和bar实例中。我只尝试过使用实体,但文档表明此方法也适用于POJO。但是,如果您的两个表具有相同名称的列,则此方法可能无法正常运行。

是的,这不起作用,因为我的实体具有相同名称的列(例如 id)... - pqvst
2
@pqvst:“我应该如何干净地构建一个兼容的返回类型,其中包括两个实体的所有字段?”-- 选择 foobar 中的一个作为 @Embedded 并将其余字段直接放在 FooAndBar 中,或者将所有字段直接放在 FooAndBar 中。在 SQL 中使用 AS 重命名结果集中的重复列,并根据需要使用 @ColumnInfo 将这些 AS 重命名的列映射到您想要的字段中。 - CommonsWare
1
@pqvst:目前来看,你的查询应该会导致SQLite输出重复列的错误,或者最好情况下,SQLiteDatabase在内部创建的Cursor将不会有两个id列(和任何其他重复的列)。在SQL查询中,需要使所有列在结果集中具有不同的名称,否则Cursor将无法拥有所有数据。一旦你解决了这个问题,就调整FooBar实体以匹配并使用@Embedded解决方案。 - CommonsWare
1
@pqvst:“对我来说感觉不太“干净””——那就去掉JOIN,分别进行两个DAO调用,一个获取Foo,另一个获取相关的Bar。Room的明确意图是任意查询都会生成任意POJO作为输出,就像你需要适当的POJO来进行Retrofit调用一样。实体主要用于简单的CRUD操作。 - CommonsWare
@adri1992 不是。我创建了一个功能请求:https://issuetracker.google.com/issues/63621744 - pqvst
显示剩余4条评论

0

这是我的食品表

@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文档,您必须使用前缀来区分它们。
在dao内部。
@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)

我在我的项目中已经完成了这个。所以你可以试一试。祝编码愉快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接