Android Room 加入的返回类型
Posted
技术标签:
【中文标题】Android Room 加入的返回类型【英文标题】:Return type for Android Room joins 【发布时间】:2017-12-17 00:21:52 【问题描述】:假设我想在两个实体 Foo
和 Bar
之间做一个 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】:是否可以强制这样的返回类型?
您可以在foo
和bar
上尝试@Embedded
注释。这将告诉 Room 尝试从您的查询中获取列并将它们倒入 foo
和 bar
实例中。我只对实体进行了尝试,但文档表明它也应该与 POJO 一起使用。
但是,如果您的两个表具有相同名称的列,这可能无法正常工作。
【讨论】:
是的,这不起作用,因为我的实体有同名的列(如id
)...
@pqvst:“我应该如何干净地构造一个兼容的返回类型,包括两个实体的所有字段?” -- 要么选择foo
或bar
为@Embedded
并将其余字段直接放入FooAndBar
,或者将所有字段直接放入FooAndBar
。在 SQL 中使用AS
重命名结果集中的重复列,并根据需要使用@ColumnInfo
将那些AS
-renamed 列映射到您想要的字段。
这正是我想要避免做的事情......我觉得不是很“干净”:/
@pqvst:就目前而言,您的查询应该导致关于输出中重复列的 SQLite 错误,或者充其量是SQLiteDatabase
在幕后创建的Cursor
不会同时具有这两者id
列(以及任何其他重复的列)。您需要 SQL 查询中的所有列在结果集中具有不同的名称,否则 Cursor
将没有所有数据。解决此问题后,调整 Foo
和 Bar
实体以匹配并使用 @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注解可以管理一对一关系
【讨论】:
如果Foo
和Bar
之间存在冲突,我相信您可以通过创建后一个类的子集表示来消除这些冲突,例如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 第二种解决方案仅在@Embedded
s 上没有设置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】:试试这个方法。
例如,我在 Product
和 Attribute
之间有 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<List<Attribute>>
更改为DataSource.Factory<Int, Attribute>
。否则,使用查询中的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 LiveData和Room:getValue返回NULL