这些有什么作用?
TypeConverters用于将 room 无法处理的类型转换为它可以处理的类型(字符串、基元、整数类型如 Integer、Long、十进制类型如 Double、Float)。
@Embedded基本上说将@Embedded 类的成员变量包含为列。例如@Embedded FoodUnitsData foodUnitsData;
。
从 Room 角度测试/验证模式
使用上面的类和用@Database ( FoodDatabase ) 注释的类中定义的实体,编译/构建项目并修复房间抱怨的任何东西是一个好主意(在这种情况下没有)。
所以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
}
- 注意见评论re DaoAccess(即注释掉该行)
然后 CTRL + F9 并检查构建日志
第四道访问
显然 FoodUnitsDataEntity 行需要添加、更新和删除。如果 Foods 对象可以驱动将 FoodUnitsDataEntity 行全部添加到一起,那也将非常方便。这需要一个带有主体的方法,因此 DaoAccess 从接口更改为抽象类以方便这种方法。
我们应该使用哪一个?
您的主要问题是FoodUnitsData列表
尽管您可以转换 List 并使用 TypeConverter,但我不建议这样做。
我会说这@Embedded
是使用的方法。连同使用@Ignore
(相反,即排除成员变量作为列)。即你会@Ignore Foods 类中的列表。
工作示例
因此,将上述内容放入一个工作示例中:-
首先是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;
}
}
- 作为用于提取 JSON 的类(其中单元将相应地填充 FoodUnitsData 对象)
- 作为房间表的模型。
其次是FoodUnitsDataEntity类。
这是一个新类,它将基于 FoodUnitsData 类,但包括 FoodsUnitsData 类不提供的两个重要值/列,即:-
- 将作为主键的唯一标识符,以及
- 用于在 Foods 表中建立行与其父级之间关系的映射/参考。由于该列将被非常频繁地使用(即它对于建立关系至关重要),因此在列上建立一个索引是有意义的(加速建立关系(就像书中的索引会加速查找内容))
- 由于存在关系,因此明智的做法是确保保持参照完整性。那就是你不想要孤立的单位。因此使用了外键约束(规则说孩子必须有父母)。
- 因为基于 FoodUnitsData 对象构建/插入会很方便,所以添加了一个构造函数,该构造函数将从 FoodUnitsData 对象(加上所有重要的 Foods 映射/引用/关联值)创建一个 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;
}
}
第四道访问
显然应该添加新的 FoodUnitsDataEntity 的惰性/更新/删除。但是请注意,现有的已更改为不返回 void 而是 long 用于插入和 int 用于更新删除。
- inserts 返回 -1 或 rowid(所有表(如果使用 Room)将具有唯一标识插入行的隐藏列)。因此,如果它是 -1,则未插入行(或 < 0)。
- 删除和更新返回受影响(更新/删除)的行数。
能够传递一个 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();
}
第六次在 Activity MainActivity中测试上述内容
这项活动将:-
- 使用一些嵌入式 FoodUnitsData 构建一个 Foods 对象。
- 将其保存为 JSON 字符串,从 JSON 字符串中提取(记录 JSON 字符串)
- 获取数据库实例。
- 获取 DaoAccess 的一个实例。
- 使用该
insertFoodsWithAllTheFoodUnitsDataEntityChildren
方法插入食物和相关/相关的孩子。
按照 :-
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"}]}
使用应用检查(数据库检查器):-

和
