我们应该如何处理Room中的嵌套对象?
此处提供了所有必需的结构:https://github.com/theoyuncu8/roomdb
JSON数据
{
"MyData": [
{
"food_id": "1",
"food_name": "Food 1",
"food_image": "imageurl",
"food_kcal": "32",
"food_url": "url",
"food_description": "desc",
"carb_percent": "72",
"protein_percent": "23",
"fat_percent": "4",
"units": [
{
"unit": "Unit A",
"amount": "735.00",
"calory": "75.757",
"calcium": "8.580",
"carbohydrt": "63.363",
"cholestrl": "63.0",
"fiber_td": "56.12",
"iron": "13.0474",
"lipid_tot": "13.01",
"potassium": "11.852",
"protein": "717.1925",
"sodium": "112.02",
"vit_a_iu": "110.7692",
"vit_c": "110.744"
},
{
"unit": "Unit C",
"amount": "32.00",
"calory": "23.757",
"calcium": "53.580",
"carbohydrt": "39.363",
"cholestrl": "39.0",
"fiber_td": "93.12",
"iron": "93.0474",
"lipid_tot": "93.01",
"potassium": "9.852",
"protein": "72.1925",
"sodium": "10.0882",
"vit_a_iu": "80.7692",
"vit_c": "80.744"
}
]
},
{
"food_id": "2",
"food_name": "Food 2",
"food_image": "imageurl",
"food_kcal": "50",
"food_url": "url",
"food_description": "desc",
"carb_percent": "25",
"protein_percent": "14",
"fat_percent": "8",
"units": [
{
"unit": "Unit A",
"amount": "25.00",
"calory": "25.757",
"calcium": "55.580",
"carbohydrt": "53.363",
"cholestrl": "53.0",
"fiber_td": "53.12",
"iron": "53.0474",
"lipid_tot": "53.01",
"potassium": "17.852",
"protein": "757.1925",
"sodium": "122.02",
"vit_a_iu": "10.7692",
"vit_c": "10.744"
},
{
"unit": "Unit C",
"amount": "2.00",
"calory": "2.757",
"calcium": "5.580",
"carbohydrt": "3.363",
"cholestrl": "3.0",
"fiber_td": "3.12",
"iron": "3.0474",
"lipid_tot": "3.01",
"potassium": "77.852",
"protein": "77.1925",
"sodium": "12.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
},
{
"unit": "Unit G",
"amount": "1.00",
"calory": "2.1",
"calcium": "0.580",
"carbohydrt": "0.363",
"cholestrl": "0.0",
"fiber_td": "0.12",
"iron": "0.0474",
"lipid_tot": "0.01",
"potassium": "5.852",
"protein": "0.1925",
"sodium": "1.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
}
]
}
]
}
实体类
食品类
public class Foods {
@SerializedName("food_id")
@Expose
private String foodId;
@SerializedName("food_name")
@Expose
private String foodName;
@SerializedName("food_image")
@Expose
private String foodImage;
@SerializedName("food_kcal")
@Expose
private String foodKcal;
@SerializedName("food_url")
@Expose
private String foodUrl;
@SerializedName("food_description")
@Expose
private String foodDescription;
@SerializedName("carb_percent")
@Expose
private String carbPercent;
@SerializedName("protein_percent")
@Expose
private String proteinPercent;
@SerializedName("fat_percent")
@Expose
private String fatPercent;
// here
@SerializedName("units")
@Expose
private List<FoodUnitsData> units = null;
// getter setter
}
FoodUnitsData类
public class FoodUnitsData {
@SerializedName("unit")
@Expose
private String unit;
@SerializedName("amount")
@Expose
private String amount;
@SerializedName("calory")
@Expose
private String calory;
@SerializedName("calcium")
@Expose
private String calcium;
@SerializedName("carbohydrt")
@Expose
private String carbohydrt;
@SerializedName("cholestrl")
@Expose
private String cholestrl;
@SerializedName("fiber_td")
@Expose
private String fiberTd;
@SerializedName("iron")
@Expose
private String iron;
@SerializedName("lipid_tot")
@Expose
private String lipidTot;
@SerializedName("potassium")
@Expose
private String potassium;
@SerializedName("protein")
@Expose
private String protein;
@SerializedName("sodium")
@Expose
private String sodium;
@SerializedName("vit_a_iu")
@Expose
private String vitAIu;
@SerializedName("vit_c")
@Expose
private String vitC;
// getter setter
}
解决方案
这些是做什么的?
TypeConverter用于将Room无法处理的类型转换为它可以处理的类型(字符串、基元、整数类型(如Integer、Long)、小数类型(如Double、Float))。
@Embedded基本上表示包含@Embedded类的成员变量作为列。例如@Embedded FoodUnitsData foodUnitsData;
。
从房间角度测试/验证架构
使用上面的类以及用@Database(FoodDatabase)注释的类中定义的实体,最好编译/生成项目并修复Room抱怨的任何问题(在本例中没有)。
因此有FoodDataabse为:-
@Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess(); //* do not inlcude this line until the DaoAccess class has been created
}
- 注意:请参阅注释RDAoAccess(即注释掉该行)
然后按CTRL+F9并检查生成日志
第四个DAoAccess
显然需要添加、更新和删除FoodUnitsDataEntity行。如果Foods对象可以驱动将FoodUnitsDataEntity行全部添加到一起,这也会非常方便。这需要具有正文的方法,因此将DaoAccess从接口更改为抽象类以便于使用此类方法。
我们应该使用哪一个?
您的主要问题是FoodUnitsData列表
虽然您可以转换列表并使用TypeConverter,但我建议您不要转换。
您可能会转换为JSON字符串(因此您从JSON提取到对象中,然后将嵌入的对象存储为JSON)。您会使数据膨胀,并使使用该数据变得困难。
例如,假设您要搜索含有1000卡路里或更多卡路里的食物,这将需要一个相当复杂的查询,或者您将加载所有数据库,然后循环浏览食物和单位。
我认为@Embedded
是要使用的方法。以及使用@Ignore
(相反,即将成员变量排除在列之外)。即您将@忽略Foods类中的列表。
使用
@Embedded
,您可以轻松地在查询中使用单个值。然后可以执行类似
SELECT * FROM the_table_used_for_the_foodunitsdata WHERE calory > 1000
的操作,您将获得返回的FoodUnitsData列表。SQLite将非常高效地执行此操作。
工作示例
因此将上述内容放入一个工作示例中:-
首先是Foods类,然后添加@Ignore注释:-
@Entity(tableName = "food_data") // ADDED to make it usable as a Room table
public class Foods {
@SerializedName("food_id")
@Expose
@PrimaryKey // ADDED as MUST have a primary key
@NonNull // ADDED Room does not accept NULLABLE PRIMARY KEY
private String foodId;
@SerializedName("food_name")
@Expose
private String foodName;
@SerializedName("food_image")
@Expose
private String foodImage;
@SerializedName("food_kcal")
@Expose
private String foodKcal;
@SerializedName("food_url")
@Expose
private String foodUrl;
@SerializedName("food_description")
@Expose
private String foodDescription;
@SerializedName("carb_percent")
@Expose
private String carbPercent;
@SerializedName("protein_percent")
@Expose
private String proteinPercent;
@SerializedName("fat_percent")
@Expose
private String fatPercent;
@SerializedName("units")
@Expose
@Ignore // ADDED AS going to be a table
private List<FoodUnitsData> units = null;
@NonNull // ADDED (not reqd)
public String getFoodId() {
return foodId;
}
public void setFoodId(@NonNull /* ADDED @NonNull (not reqd)*/ String foodId) {
this.foodId = foodId;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public String getFoodImage() {
return foodImage;
}
public void setFoodImage(String foodImage) {
this.foodImage = foodImage;
}
public String getFoodKcal() {
return foodKcal;
}
public void setFoodKcal(String foodKcal) {
this.foodKcal = foodKcal;
}
public String getFoodUrl() {
return foodUrl;
}
public void setFoodUrl(String foodUrl) {
this.foodUrl = foodUrl;
}
public String getFoodDescription() {
return foodDescription;
}
public void setFoodDescription(String foodDescription) {
this.foodDescription = foodDescription;
}
public String getCarbPercent() {
return carbPercent;
}
public void setCarbPercent(String carbPercent) {
this.carbPercent = carbPercent;
}
public String getProteinPercent() {
return proteinPercent;
}
public void setProteinPercent(String proteinPercent) {
this.proteinPercent = proteinPercent;
}
public String getFatPercent() {
return fatPercent;
}
public void setFatPercent(String fatPercent) {
this.fatPercent = fatPercent;
}
public List<FoodUnitsData> getUnits() {
return units;
}
public void setUnits(List<FoodUnitsData> units) {
this.units = units;
}
}
- Foods类现在有两个用途:-
- 作为提取JSON的类(其中将相应地使用FoodUnitsData对象填充单元)
- 作为Room表的模型。
- 查看评论
第二个FoodUnitsDataEntity类。
这是一个新类,它将基于FoodUnitsData类,但包括FoodsUnitsData类不支持的两个重要值/列,即:-
- 将作为主键的唯一标识符,
- 用于在Foods表中建立行及其父级之间关系的映射/引用。由于此列将被频繁使用(即,它对于建立关系至关重要),因此在列上建立索引是有意义的(加快建立关系的速度(就像书中的索引将加快查找内容一样))
- 由于存在关系,因此明智的做法是确保维护引用完整性。那就是你不想要孤儿部队。因此,使用了外键约束(规定子级必须有父级的规则)。
- 由于基于FoodUnitsData对象生成/插入将非常方便,因此添加了一个构造函数,该构造函数将从FoodUnitsData对象(加上所有重要的食品映射/引用/关联值)创建一个FoodUnitsDataEnity对象。
所以:-
/*
NEW CLASS that:-
Has a Unique ID (Long most efficient) as the primary Key
Has a column to reference/map to the parent FoodUnitsData of the food that owns this
Embeds the FoodUnitsData class
Enforces referential integrity be defining a Foreign Key constraint (optional)
If parent is delete then children are deleted (CASCADE)
If the parent's foodId column is changed then the foodIdMap is updated in the children (CASCADE)
*/
@Entity(
tableName = "food_units",
foreignKeys = {
@ForeignKey(
entity = Foods.class, /* The class (annotated with @ Entity) of the owner/parent */
parentColumns = {"foodId"}, /* respective column referenced in the parent (Foods) */
childColumns = {"foodIdMap"}, /* Column in the table that references the parent */
onDelete = CASCADE, /* optional within Foreign key */
onUpdate = CASCADE /* optional with foreign key */
)
}
)
class FoodUnitsDataEntity {
@PrimaryKey
Long foodUnitId = null;
@ColumnInfo(index = true)
String foodIdMap;
@Embedded
FoodUnitsData foodUnitsData;
FoodUnitsDataEntity(){}
FoodUnitsDataEntity(FoodUnitsData fud, String foodId) {
this.foodUnitsData = fud;
this.foodIdMap = foodId;
this.foodUnitId = null;
}
}
第三个FoodUnitsData类
这个类是可以接受的。但是,对于演示/示例,构造函数按如下方式添加:-
public class FoodUnitsData {
@SerializedName("unit")
@Expose
private String unit;
@SerializedName("amount")
@Expose
private String amount;
@SerializedName("calory")
@Expose
private String calory;
@SerializedName("calcium")
@Expose
private String calcium;
@SerializedName("carbohydrt")
@Expose
private String carbohydrt;
@SerializedName("cholestrl")
@Expose
private String cholestrl;
@SerializedName("fiber_td")
@Expose
private String fiberTd;
@SerializedName("iron")
@Expose
private String iron;
@SerializedName("lipid_tot")
@Expose
private String lipidTot;
@SerializedName("potassium")
@Expose
private String potassium;
@SerializedName("protein")
@Expose
private String protein;
@SerializedName("sodium")
@Expose
private String sodium;
@SerializedName("vit_a_iu")
@Expose
private String vitAIu;
@SerializedName("vit_c")
@Expose
private String vitC;
/* ADDED Constructors */
FoodUnitsData(){}
FoodUnitsData(String unit,
String amount,
String calory,
String calcium,
String cholestrl,
String carbohydrt,
String fiberTd,
String iron,
String lipidTot,
String potassium,
String protein,
String sodium,
String vitAIu,
String vitC
){
this.unit = unit;
this.amount = amount;
this.calory = calory;
this.calcium = calcium;
this.cholestrl = cholestrl;
this.carbohydrt = carbohydrt;
this.fiberTd = fiberTd;
this.iron = iron;
this.lipidTot = lipidTot;
this.potassium = potassium;
this.sodium = sodium;
this.protein = protein;
this.vitAIu = vitAIu;
this.vitC = vitC;
}
/* Finish of ADDED code */
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public String getCalory() {
return calory;
}
public void setCalory(String calory) {
this.calory = calory;
}
public String getCalcium() {
return calcium;
}
public void setCalcium(String calcium) {
this.calcium = calcium;
}
public String getCarbohydrt() {
return carbohydrt;
}
public void setCarbohydrt(String carbohydrt) {
this.carbohydrt = carbohydrt;
}
public String getCholestrl() {
return cholestrl;
}
public void setCholestrl(String cholestrl) {
this.cholestrl = cholestrl;
}
public String getFiberTd() {
return fiberTd;
}
public void setFiberTd(String fiberTd) {
this.fiberTd = fiberTd;
}
public String getIron() {
return iron;
}
public void setIron(String iron) {
this.iron = iron;
}
public String getLipidTot() {
return lipidTot;
}
public void setLipidTot(String lipidTot) {
this.lipidTot = lipidTot;
}
public String getPotassium() {
return potassium;
}
public void setPotassium(String potassium) {
this.potassium = potassium;
}
public String getProtein() {
return protein;
}
public void setProtein(String protein) {
this.protein = protein;
}
public String getSodium() {
return sodium;
}
public void setSodium(String sodium) {
this.sodium = sodium;
}
public String getVitAIu() {
return vitAIu;
}
public void setVitAIu(String vitAIu) {
this.vitAIu = vitAIu;
}
public String getVitC() {
return vitC;
}
public void setVitC(String vitC) {
this.vitC = vitC;
}
}
第四个DAoAccess
显然应该添加新FoodUnitsDataEntity的INTERT/UPDATE/DELETE。但是,请注意,现有文件已更改为不返回void,而是为插入返回long,为更新删除返回int。
- INSERT返回r-1或rowid(所有表(如果使用Room)都将具有唯一标识插入行的隐藏列)。因此,如果为-1,则未插入行(或<;0)。
- DELETE和UPDATE返回受影响(更新/删除)的行数。
能够传递Food对象并插入所有单位行将是有益的。因为这需要具有主体而不是接口的方法将使用抽象类。
因此DAoAccess变为:-
@Dao
public /* CHANGED TO abstract class from interface */ abstract class DaoAccess {
@Query("SELECT * FROM food_data")
abstract List<Foods> getAll();
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Foods task);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(FoodUnitsDataEntity foodUnitsDataEntity);
@Delete
abstract int delete(Foods task);
@Delete
abstract int delete(FoodUnitsDataEntity foodUnitsDataEntity);
@Update
abstract int update(Foods task);
@Update
abstract int update(FoodUnitsDataEntity foodUnitsDataEntity);
@Query("") /* Trick Room to allow the use of @Transaction*/
@Transaction
long insertFoodsWithAllTheFoodUnitsDataEntityChildren(Foods foods) {
long rv = -1;
long fudInsertCount = 0;
if (insert(foods) > 0) {
for(FoodUnitsData fud: foods.getUnits()) {
if (insert(new FoodUnitsDataEntity(fud,foods.getFoodId())) > 0) {
fudInsertCount++;
}
}
if (fudInsertCount != foods.getUnits().size()) {
rv = -(foods.getUnits().size() - fudInsertCount);
} else {
rv = 0;
}
}
return rv;
}
}
第五个食品数据库
只需将FoodUnitsDataEntity添加为实体:-
@Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess();
}
第六次在活动中测试上述内容MainActivity
本练习将:-
- 使用一些嵌入的FoodUnitsData生成Foods对象。
- 将其另存为JSON字符串,从JSON字符串中提取(日志记录 JSON字符串)
- 获取数据库的实例。
- 获取DAoAccess的实例。
- 使用
insertFoodsWithAllTheFoodUnitsDataEntityChildren
方法插入Foods和关联/相关的子项。
根据:-
public class MainActivity extends AppCompatActivity {
FoodDatabase fooddb;
DaoAccess foodDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Build data to test */
Foods foods = new Foods();
foods.setFoodId("MyFood");
foods.setCarbPercent("10.345");
foods.setFoodDescription("The Food");
foods.setFatPercent("15.234");
foods.setFoodImage("The Food Image");
foods.setFoodKcal("120");
foods.setFoodName("The Food");
foods.setFoodUrl("URL for the Food");
foods.setProteinPercent("16.234");
foods.setUnits(Arrays.asList(
new FoodUnitsData("100","15","1200","11","12","13","14","15","16","17","18","19","20","21"),
new FoodUnitsData("1001","151","12001","11","12","13","14","15","16","17","18","19","20","21"),
new FoodUnitsData("1002","152","12002","11","12","13","14","15","16","17","18","19","20","21")
));
String json = new Gson().toJson(foods);
Log.d("JSONINFO",json);
Foods foodsFromJSON = new Gson().fromJson(json,Foods.class);
fooddb = Room.databaseBuilder(this,FoodDatabase.class,"food.db")
.allowMainThreadQueries()
.build();
foodDao = fooddb.daoAccess();
foodDao.insertFoodsWithAllTheFoodUnitsDataEntityChildren(foodsFromJSON);
}
}
运行应用程序后的结果
日志包括:-
D/JSONINFO: {"carb_percent":"10.345","fat_percent":"15.234","food_description":"The Food","food_id":"MyFood","food_image":"The Food Image","food_kcal":"120","food_name":"The Food","food_url":"URL for the Food","protein_percent":"16.234","units":[{"amount":"15","calcium":"11","calory":"1200","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"100","vit_a_iu":"20","vit_c":"21"},{"amount":"151","calcium":"11","calory":"12001","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"1001","vit_a_iu":"20","vit_c":"21"},{"amount":"152","calcium":"11","calory":"12002","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"1002","vit_a_iu":"20","vit_c":"21"}]}
使用应用检查(数据库检查器):-
和
相关文章