I want to create a database like below. I followed the documentation but couldn't find any similar examples. Can you help me? There will be days and within these days there will be multiple Daily Meals and Daily Water tables
A day database will be created as soon as the app is started and then whatever day it is in (can change the dates manually) daily water and daily meals will be added into that day
"days":
[
{
"id": "Integer",
"date": "String",
"weight": "Float",
"height": "Integer",
"waist_circumference": "Integer",
"neck_circumference": "Integer",
"hip_circumference": "Integer",
"steps": "Long",
"taken_kcal": "Float",
"burned_kcal": "Float",
"carb": "Float",
"protein": "Float",
"fat": "Float",
"water_ml": "Integer",
"water_percent": "Float",
"meals":
[
{
"day_id": "Integer",
"id": "Integer",
"food_meal_time": "String",
"food_name": "String",
"food_image": "String",
"food_kcal": "Float",
"food_portion": "String",
"food_carb": "Float",
"food_protein": "Float",
"food_fat": "Float"
},
{
"day_id": "Integer",
"id": "Integer",
"food_meal_time": "String",
"food_name": "String",
"food_image": "String",
"food_kcal": "Float",
"food_portion": "String",
"food_carb": "Float",
"food_protein": "Float",
"food_fat": "Float"
}
],
"water":
[
{
"day_id": "Integer",
"id": "Integer",
"water_ml": "Float",
"water_percent": "Float",
"water_time": "Long"
},
{
"day_id": "Integer",
"id": "Integer",
"water_ml": "Float",
"water_percent": "Float",
"water_time": "Long"
},
{
"day_id": "Integer",
"id": "Integer",
"water_ml": "Float",
"water_percent": "Float",
"water_time": "Long"
}
]
}
]
You have 3 core tables (@Entity
annotated classes defined in the entities list of the @Database
annotation), Days, Meals and Water.
If (see However below) you want a many-many relationship between Days and Meals and also between Days and Water then you would have an associative/mapping/reference (and other terms) table for each many-many relationship. Such a table has two columns one that uniquely associates/references/maps to the Day and the other that uniquely associates/reference/maps to the Meals, for the Days-Meals or in the case of the Days-Water associates/references/maps to the Water. Typically the association/reference/map will be to the id column.
So in your case (if as per the heading) you would have two of these tables.
To extract related data (Days with the related Meals and with the related Waters) then you have a POJO that has the Days @Embedded
and an @Relation
to the Meals, defining the associative/reference/mapping table using the @Associate
and the @Junction
annotation, likewise an @Relation
to the Waters.
However, your JSON describes two one-many relationships rather then two many-many relationships. That is the Meals have a reference to the parent Days and likewise the Waters have a reference to the parent Days.
In this case you just need the 3 core tables. For extracting the Days with the related Meals and also with the related Waters. You use the @Embedded
annotation for the Days and @Relation
for both the Meals and the Waters without the @Associate
and @Juntion
as there is no associative/reference/mapping table.
Working Example This working example uses both a
So the 3 core tables (entities) Day, Water and Meal.
Day (note a row is a Day so typically it is the Day table rather than Days table) :-
@Entity
public class Day {
@PrimaryKey
Long dayId = null;
String date;
Float weight;
/* etc */
Day(){} /* Room will use this constructor */
@Ignore /* Tell Room to ignore this constructor, but still usable outside of Room, so makes life easier */
Day(String date, Float weight) {
this.dayId = null; /* not needed but effectively what happens */
this.date = date;
this.weight = weight;
}
/* No getters and setters required as members aren't private/protected */
}
Meal
@Entity(
/* Optional but suggested */
foreignKeys = {
@ForeignKey(
entity = Day.class,
parentColumns = {"dayId"},
childColumns = {"dayId_Meal_Map"}
/* Optional but can be useful */
, onDelete = ForeignKey.CASCADE
, onUpdate = ForeignKey.CASCADE
)
}
)
class Meal {
@PrimaryKey
Long mealId=null;
@ColumnInfo(index = true)
long dayId_Meal_Map;
String food_meal_time;
/* etc */
Meal(){}
@Ignore
Meal(long parentDayId, String food_meal_time) {
this.dayId_Meal_Map = parentDayId;
this.food_meal_time = food_meal_time;
}
@Ignore
Meal(String food_meal_time) {
this.food_meal_time = food_meal_time;
}
}
Water
@Entity(
/* Optional but suggested */
foreignKeys = {
@ForeignKey(
entity = Day.class,
parentColumns = {"dayId"},
childColumns = {"dayId_Water_Map"}
/* Optional but can be useful */
, onDelete = ForeignKey.CASCADE
, onUpdate = ForeignKey.CASCADE
)
}
)
class Water {
@PrimaryKey
Long waterId=null;
@ColumnInfo(index = true)
long dayId_Water_Map;
Float water_ml;
/* etc */
Water(){}
@Ignore
Water(long parentDayId, Float water_ml) {
this.dayId_Water_Map = parentDayId;
this.water_ml = water_ml;
}
/* Constructor for m2m i.e. reference to dayId is not needed as in the mapping table */
@Ignore
Water(Float water_ml) {
this.water_ml = water_ml;
}
}
For the Many-Many relationship then the 2 mapping tables DayMealMap and DayWaterMap
DayMealMap
@Entity(
primaryKeys = {"dayMealMap_dayId_Reference","dayMealMap_mealId_Reference"},
/* Optional but suggested */
foreignKeys = {
@ForeignKey(
entity = Day.class,
parentColumns = {"dayId"},
childColumns = {"dayMealMap_dayId_Reference"}
/* Optional but helpful */
, onDelete = ForeignKey.CASCADE
, onUpdate = ForeignKey.CASCADE
),
@ForeignKey(
entity = Meal.class,
parentColumns = "mealId",
childColumns = "dayMealMap_mealId_Reference"
, onDelete = ForeignKey.CASCADE
, onUpdate = ForeignKey.CASCADE
)
}
)
class DayMealMap {
long dayMealMap_dayId_Reference;
/* Index optional but would likely improve efficiency */
/* Also ROOM will issue warning if not indexed */
@ColumnInfo(index = true)
long dayMealMap_mealId_Reference;
DayMealMap(){}
@Ignore
DayMealMap(long dayIdMap, long mealIdMap) {
this.dayMealMap_dayId_Reference = dayIdMap;
this.dayMealMap_mealId_Reference = mealIdMap;
}
}
DayWaterMap
@Entity(
primaryKeys = {"dayWaterMap_dayId_Reference","dayWaterMap_waterId_Reference"},
foreignKeys = {
@ForeignKey(
entity = Day.class,
parentColumns = "dayId",
childColumns = "dayWaterMap_dayId_Reference"
, onDelete = ForeignKey.CASCADE
, onUpdate = ForeignKey.CASCADE
),
@ForeignKey(
entity = Water.class,
parentColumns = "waterId",
childColumns = "dayWaterMap_waterId_Reference"
,onDelete = ForeignKey.CASCADE
,onUpdate = ForeignKey.CASCADE
)
}
)
class DayWaterMap {
long dayWaterMap_dayId_Reference;
@ColumnInfo(index = true)
long dayWaterMap_waterId_Reference;
DayWaterMap(){}
@Ignore
DayWaterMap(long dayIdMap, long waterIdMap) {
this.dayWaterMap_dayId_Reference = dayIdMap;
this.dayWaterMap_waterId_Reference = waterIdMap;
}
}
For extract of the Days with the Related Meals and Waters via the 1-M relationship then a POJO
class DayWithRelatedMealsAlsoWithRelatedWaters {
@Embedded
Day day;
@Relation(
entity = Meal.class,
parentColumn = "dayId",
entityColumn = "dayId_Meal_Map"
)
List<Meal> mealList;
@Relation(
entity = Water.class,
parentColumn = "dayId",
entityColumn = "dayId_Water_Map"
)
List<Water> waterList;
}
For the Many-Many, to get the same related data it's POJO (via the mapping table (aka the associative tables))
class M2MDayWithRelatedMealsAlsoWithRealtedWaters {
@Embedded
Day day;
@Relation(
entity = Meal.class,
parentColumn = "dayId",
entityColumn = "mealId",
associateBy = @Junction(
value = DayMealMap.class,
parentColumn = "dayMealMap_dayId_Reference",
entityColumn = "dayMealMap_mealId_Reference"
)
)
List<Meal> mealList;
@Relation(
entity = Water.class,
parentColumn = "dayId",
entityColumn = "waterId",
associateBy = @Junction(
value = DayWaterMap.class,
parentColumn = "dayWaterMap_dayId_Reference",
entityColumn = "dayWaterMap_waterId_Reference"
)
)
List<Water> waterList;
}
An @Dao
annotated abstract class (could be an interface but I prefer an abstract class as it is more flexible (reasons beyond this question))
AllDao (i.e. All of the DAO methods in one, for convenience):-
@Dao
abstract class AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Day day);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Meal meal);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Water water);
/* For many-many mapping table inserts */
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(DayMealMap dayMealMap);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(DayWaterMap dayWaterMap);
@Transaction
@Query("SELECT * FROM day")
abstract List<DayWithRelatedMealsAlsoWithRelatedWaters> getListOfDaysWithRelatedMealsAndRelatedWaters();
@Transaction
@Query("SELECT * FROM day")
abstract List<M2MDayWithRelatedMealsAlsoWithRealtedWaters> getM2mListOfDaysWithRelatedMealsAlsoWithRelatedWaters();
}
Starting to put it all together from a Room perspective the @Database
annotated class.
@Database(
entities = {
/* Either 1-many or many-many use the 3 core tables */
Day.class,
Meal.class,
Water.class
/* for many-many then the 2 mapping tables */
, DayMealMap.class
, DayWaterMap.class
},
version = 1,
exportSchema = false
)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private volatile static TheDatabase INSTANCE= null;
public static TheDatabase getINSTANCE(Context context) {
if (INSTANCE==null) {
INSTANCE = Room.databaseBuilder(context,TheDatabase.class,"the_database.db")
.allowMainThreadQueries()
.build();
}
return INSTANCE;
}
}
.allowMainThreadQueries
used for convenience and brevity/clarity.Actually using all of the above to add some data and to then extract the data for both types of realtionships:-
MainActivity
public class MainActivity extends AppCompatActivity {
TheDatabase mDB;
AllDao mDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDB = TheDatabase.getINSTANCE(this);
mDao = mDB.getAllDao();
/* 1-many */
long day001Id = mDao.insert(new Day("2022-07-08",10.10F));
long day002Id = mDao.insert(new Day("2022-07-09",11.11F));
long day003Id = mDao.insert(new Day("2022-07-09",12.12F));
/* Add some related Meals (1-M) */
long m011Id = mDao.insert(new Meal(day001Id,"09:00"));
long m012Id = mDao.insert(new Meal(day001Id,"11:00"));
long m013Id = mDao.insert(new Meal(day001Id,"13:00"));
long m014Id = mDao.insert(new Meal(day001Id,"18:00"));
long m015Id = mDao.insert(new Meal(day001Id,"21:00"));
long m021Id = mDao.insert(new Meal(day002Id,"09:30"));
long m022Id = mDao.insert(new Meal(day002Id,"10:15"));
long m023Id = mDao.insert(new Meal(day002Id,"12:45"));
long m024Id = mDao.insert(new Meal(day002Id,"17:30"));
long m025Id = mDao.insert(new Meal(day002Id,"20:30"));
/* Add some Waters (1-M) */
long w011Id = mDao.insert(new Water(day001Id,100.100F));
long w012Id = mDao.insert(new Water(day001Id,200.200F));
long w021Id = mDao.insert(new Water(day002Id, 120.12F));
long w022Id = mDao.insert(new Water(day002Id, 220.22F));
/* Use the above Days, Meals and Waters to add M2M stuff */
/* Day 1 just a subset of the 1-m's */
mDao.insert(new DayMealMap(day001Id,m011Id));
mDao.insert(new DayMealMap(day001Id,m013Id));
mDao.insert(new DayMealMap(day001Id,m015Id));
/* Day 2 just a subset of 1-m's */
mDao.insert(new DayMealMap(day002Id, m022Id));
mDao.insert(new DayMealMap(day002Id,m024Id));
/* Note with 1-M day 3 was empty, this relates existing meals from day 1 and day 2 to day 3 */
mDao.insert(new DayMealMap(day003Id,m011Id));
mDao.insert(new DayMealMap(day003Id,m021Id));
for (DayWithRelatedMealsAlsoWithRelatedWaters d: mDao.getListOfDaysWithRelatedMealsAndRelatedWaters()) {
logADayWithRelatedMealsAndWaters(d,"_1-M");
}
for (M2MDayWithRelatedMealsAlsoWithRealtedWaters m: mDao.getM2mListOfDaysWithRelatedMealsAlsoWithRelatedWaters()) {
logM2MDayWithRelatedMealsAndWaters(m,"_M-M");
}
}
private void logADayWithRelatedMealsAndWaters(DayWithRelatedMealsAlsoWithRelatedWaters dwrmawrw, String suffix) {
Log.d(
"DAYINFO" + suffix,
"Day is " + dwrmawrw.day.date +
" Weight is " + dwrmawrw.day.weight +
" ID is " + dwrmawrw.day.dayId
+ " The Day has " + dwrmawrw.mealList.size() + " meal(s) "
+ " and it has " + dwrmawrw.waterList.size() + " water(s)."
);
for (Meal m: dwrmawrw.mealList) {
Log.d(
"DAYINFO" + suffix,
"\tMeal is " + m.food_meal_time + " it's parent Day has the ID " + m.dayId_Meal_Map
);
}
for (Water w: dwrmawrw.waterList) {
Log.d(
"DAYINFO" + suffix,
"\tWater is " + w.water_ml + " it's parent Day has the ID " + w.dayId_Water_Map
);
}
}
private void logM2MDayWithRelatedMealsAndWaters(M2MDayWithRelatedMealsAlsoWithRealtedWaters mdwrmawrw, String suffix) {
Log.d(
"DAYINFO" + suffix,
"Day is " + mdwrmawrw.day.date +
" Weight is " + mdwrmawrw.day.weight +
" ID is " + mdwrmawrw.day.dayId
+ " The Day has " + mdwrmawrw.mealList.size() + " meal(s) "
+ " and it has " + mdwrmawrw.waterList.size() + " water(s)."
);
for (Meal m: mdwrmawrw.mealList) {
Log.d(
"DAYINFO" + suffix,
"\tMeal is " + m.food_meal_time + " it's parent Day has the ID " + m.dayId_Meal_Map
);
}
for (Water w: mdwrmawrw.waterList) {
Log.d(
"DAYINFO" + suffix,
"\tWater is " + w.water_ml + " it's parent Day has the ID " + w.dayId_Water_Map
);
}
}
}
THE RESULT (i.e. the output written to the log, with spaces added to split the two types of relationshsips):-
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Day is 2022-07-08 Weight is 10.1 ID is 1 The Day has 5 meal(s) and it has 2 water(s).
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Meal is 09:00 it's parent Day has the ID 1
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Meal is 11:00 it's parent Day has the ID 1
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Meal is 13:00 it's parent Day has the ID 1
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Meal is 18:00 it's parent Day has the ID 1
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Meal is 21:00 it's parent Day has the ID 1
2022-07-08 12:20:01.522 D/DAYINFO_1-M: Water is 100.1 it's parent Day has the ID 1
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Water is 200.2 it's parent Day has the ID 1
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Day is 2022-07-09 Weight is 11.11 ID is 2 The Day has 5 meal(s) and it has 2 water(s).
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Meal is 09:30 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Meal is 10:15 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Meal is 12:45 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Meal is 17:30 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Meal is 20:30 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Water is 120.12 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Water is 220.22 it's parent Day has the ID 2
2022-07-08 12:20:01.523 D/DAYINFO_1-M: Day is 2022-07-09 Weight is 12.12 ID is 3 The Day has 0 meal(s) and it has 0 water(s).
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Day is 2022-07-08 Weight is 10.1 ID is 1 The Day has 3 meal(s) and it has 0 water(s).
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Meal is 09:00 it's parent Day has the ID 1
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Meal is 13:00 it's parent Day has the ID 1
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Meal is 21:00 it's parent Day has the ID 1
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Day is 2022-07-09 Weight is 11.11 ID is 2 The Day has 2 meal(s) and it has 0 water(s).
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Meal is 10:15 it's parent Day has the ID 2
2022-07-08 12:20:01.527 D/DAYINFO_M-M: Meal is 17:30 it's parent Day has the ID 2
2022-07-08 12:20:01.528 D/DAYINFO_M-M: Day is 2022-07-09 Weight is 12.12 ID is 3 The Day has 2 meal(s) and it has 0 water(s).
2022-07-08 12:20:01.528 D/DAYINFO_M-M: Meal is 09:00 it's parent Day has the ID 1
2022-07-08 12:20:01.528 D/DAYINFO_M-M: Meal is 09:30 it's parent Day has the ID 2