Room 中的嵌套对象应该怎么办?
What should we do for nested objects in Room?
如果下面有像我的JSON结构这样的结构,我们应该如何创建EntityClasses呢?没有这方面的例子。虽然在很久以前写的文章中@embeded 用于内部数组,但现在使用了类似转换器的结构。我们应该使用哪一个?这些有什么作用?如何创建我的类型的结构?请在Java
中提供帮助
此处提供所有必需的结构: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"
}
]
}
]
}
实体Class
食品Class
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 Class
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
}
将它们分成 2 个实体,而不是创建关系 class。此关系 class 使用 FoodListModel 作为嵌入式 属性 与 UnitList 作为列表相关。
What do these do?
TypeConverters 用于将 room 无法处理的类型转换为它可以处理的类型(字符串、基元、整数类型如 Integer、Long、小数类型如 Double , 浮动).
@Embedded 基本上是说将@Embedded class 的成员变量包含为列。例如@Embedded FoodUnitsData foodUnitsData;
.
Test/Verify Room 视角下的 Schema
使用上面的 class 和 class 中定义的实体并用 @Database (FoodDatabase) 注释,这将是一个好主意 compile/build 项目并修复房间抱怨的任何问题(none 在这种情况下)。
因此 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
}
- 注意查看有关 DaoAccess 的评论(即注释掉该行)
然后 CTRL + F9 并检查构建日志
第四个DaoAccess
显然需要添加、更新和删除 FoodUnitsDataEntity 行。如果 Foods object 可以驱动将 FoodUnitsDataEntity 行全部添加到一个中,那也会非常方便。这需要一个带有主体的方法,因此 DaoAccess 从接口更改为抽象 class 以促进这种方法。
Which one should we use?
您的主要问题是 FoodUnitsData
的列表
尽管您可以转换 List 并使用 TypeConverter,但我建议您不要这样做。
您可能会转换为 JSON 字符串 (因此您从 JSON 中提取到 object 中,然后存储嵌入的 objects 为 JSON)。您使数据膨胀并使使用该数据变得困难。
举例来说,您想搜索含有 1000 卡路里或更多卡路里的食物,这将需要非常复杂的查询,或者您将加载所有数据库,然后遍历食物,然后单位。
我会说@Embedded
是使用的方法。随着使用 @Ignore
(相反,即将成员变量排除在列之外)。也就是说,您会@Ignore 食品列表 class.
使用 @Embedded
,您可以轻松地在查询中使用单个值。
然后你可以做类似 SELECT * FROM the_table_used_for_the_foodunitsdata WHERE calory > 1000
的事情,你会得到一个 FoodUnitsData 列表 returned。 SQLite 会非常有效地完成这项工作。
工作示例
所以将上面的内容放到一个工作示例中:-
首先是 Foods class 并添加 @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;
}
}
- 食物 class现在有两个用途:-
- 作为提取 JSON 的 class(其中单位将相应地填充 FoodUnitsData objects)
- 作为房间的模型 table。
- 查看评论
第二个 FoodUnitsDataEntity class.
这是一个新的 class,它将基于 FoodUnitsData class,但包括两个重要的 values/columns FoodsUnitsData class 未提供的内容,即:-
- 将作为主键的唯一标识符,并且
- a map/reference 用于在 Foods table 中建立行与它的 parent 之间的关系。由于此列将被频繁使用(即它对于建立关系至关重要),因此在该列上有一个索引是有意义的(加快建立关系(就像书中的索引会加快查找东西的速度))
- 由于存在关系,因此确保保持引用完整性是明智的。那就是你不想要孤立的单位。因此使用了外键约束(一条规则说 child 必须有一个 parent)。
- 因为基于 FoodUnitsData object build/insert 会很方便,所以添加了一个构造函数,该构造函数将从 FoodUnitsData object 创建 FoodUnitsDataEnity object(加上所有重要的食物 mapping/referencing/associating 值)。
所以 :-
/*
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 class
这个class就这样好了。但是,对于 demo/example 构造函数是按照 :-
添加的
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
显然 inerts/updates/ 应该添加新 FoodUnitsDataEntity 的删除。但是请注意,现有的已更改为不是 return void,而是 long 用于插入和 int 用于更新删除。
- 插入 return eihr -1 或 rowid(所有 tables(如果使用 Room)都将具有的隐藏列,用于唯一标识插入的行)。因此,如果它是 -1,则未插入行(或 < 0)。
- 删除并更新 return 受影响的 (updated/deleted) 行数。
如果能够传递食物 object 并插入所有单位行,那将是有益的。因为这需要一个带有主体而不是 接口 和 抽象的方法class将被使用。
所以 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;
}
}
第五个FoodDatabase
只需添加 FoodUnitsDataEntity 作为一个实体 :-
@Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess();
}
在 Activity MainActivity
中进行上述第六次测试
这个activity将:-
- 使用一些嵌入式 FoodUnitsData 构建一个 Foods object。
- 保存为JSON字符串,从JSON字符串中提取(logging
JSON 字符串)
- 获取数据库实例。
- 获取 DaoAccess 实例。
- 使用
insertFoodsWithAllTheFoodUnitsDataEntityChildren
方法插入食物和assoctiated/related children.
根据 :-
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"}]}
使用App Inspection(数据库检查器):-
和
如果下面有像我的JSON结构这样的结构,我们应该如何创建EntityClasses呢?没有这方面的例子。虽然在很久以前写的文章中@embeded 用于内部数组,但现在使用了类似转换器的结构。我们应该使用哪一个?这些有什么作用?如何创建我的类型的结构?请在Java
中提供帮助此处提供所有必需的结构: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"
}
]
}
]
}
实体Class
食品Class
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 Class
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
}
将它们分成 2 个实体,而不是创建关系 class。此关系 class 使用 FoodListModel 作为嵌入式 属性 与 UnitList 作为列表相关。
What do these do?
TypeConverters 用于将 room 无法处理的类型转换为它可以处理的类型(字符串、基元、整数类型如 Integer、Long、小数类型如 Double , 浮动).
@Embedded 基本上是说将@Embedded class 的成员变量包含为列。例如@Embedded FoodUnitsData foodUnitsData;
.
Test/Verify Room 视角下的 Schema
使用上面的 class 和 class 中定义的实体并用 @Database (FoodDatabase) 注释,这将是一个好主意 compile/build 项目并修复房间抱怨的任何问题(none 在这种情况下)。
因此 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
}
- 注意查看有关 DaoAccess 的评论(即注释掉该行)
然后 CTRL + F9 并检查构建日志
第四个DaoAccess
显然需要添加、更新和删除 FoodUnitsDataEntity 行。如果 Foods object 可以驱动将 FoodUnitsDataEntity 行全部添加到一个中,那也会非常方便。这需要一个带有主体的方法,因此 DaoAccess 从接口更改为抽象 class 以促进这种方法。
Which one should we use?
您的主要问题是 FoodUnitsData
的列表尽管您可以转换 List 并使用 TypeConverter,但我建议您不要这样做。
您可能会转换为 JSON 字符串 (因此您从 JSON 中提取到 object 中,然后存储嵌入的 objects 为 JSON)。您使数据膨胀并使使用该数据变得困难。
举例来说,您想搜索含有 1000 卡路里或更多卡路里的食物,这将需要非常复杂的查询,或者您将加载所有数据库,然后遍历食物,然后单位。
我会说@Embedded
是使用的方法。随着使用 @Ignore
(相反,即将成员变量排除在列之外)。也就是说,您会@Ignore 食品列表 class.
使用
@Embedded
,您可以轻松地在查询中使用单个值。然后你可以做类似
SELECT * FROM the_table_used_for_the_foodunitsdata WHERE calory > 1000
的事情,你会得到一个 FoodUnitsData 列表 returned。 SQLite 会非常有效地完成这项工作。
工作示例
所以将上面的内容放到一个工作示例中:-
首先是 Foods class 并添加 @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;
}
}
- 食物 class现在有两个用途:-
- 作为提取 JSON 的 class(其中单位将相应地填充 FoodUnitsData objects)
- 作为房间的模型 table。
- 查看评论
第二个 FoodUnitsDataEntity class.
这是一个新的 class,它将基于 FoodUnitsData class,但包括两个重要的 values/columns FoodsUnitsData class 未提供的内容,即:-
- 将作为主键的唯一标识符,并且
- a map/reference 用于在 Foods table 中建立行与它的 parent 之间的关系。由于此列将被频繁使用(即它对于建立关系至关重要),因此在该列上有一个索引是有意义的(加快建立关系(就像书中的索引会加快查找东西的速度))
- 由于存在关系,因此确保保持引用完整性是明智的。那就是你不想要孤立的单位。因此使用了外键约束(一条规则说 child 必须有一个 parent)。
- 因为基于 FoodUnitsData object build/insert 会很方便,所以添加了一个构造函数,该构造函数将从 FoodUnitsData object 创建 FoodUnitsDataEnity object(加上所有重要的食物 mapping/referencing/associating 值)。
所以 :-
/*
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 class
这个class就这样好了。但是,对于 demo/example 构造函数是按照 :-
添加的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
显然 inerts/updates/ 应该添加新 FoodUnitsDataEntity 的删除。但是请注意,现有的已更改为不是 return void,而是 long 用于插入和 int 用于更新删除。
- 插入 return eihr -1 或 rowid(所有 tables(如果使用 Room)都将具有的隐藏列,用于唯一标识插入的行)。因此,如果它是 -1,则未插入行(或 < 0)。
- 删除并更新 return 受影响的 (updated/deleted) 行数。
如果能够传递食物 object 并插入所有单位行,那将是有益的。因为这需要一个带有主体而不是 接口 和 抽象的方法class将被使用。
所以 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;
}
}
第五个FoodDatabase
只需添加 FoodUnitsDataEntity 作为一个实体 :-
@Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess();
}
在 Activity MainActivity
中进行上述第六次测试这个activity将:-
- 使用一些嵌入式 FoodUnitsData 构建一个 Foods object。
- 保存为JSON字符串,从JSON字符串中提取(logging JSON 字符串)
- 获取数据库实例。
- 获取 DaoAccess 实例。
- 使用
insertFoodsWithAllTheFoodUnitsDataEntityChildren
方法插入食物和assoctiated/related children.
根据 :-
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"}]}
使用App Inspection(数据库检查器):-
和