Room is an object mapping library that helps us, the Android developers, to handle an SQLite database in Android development.
If you want to learn more about the main components from Room you could check the next two articles:
SQLite is a relational database so it understands the relations between the entities. In Room, Google decided that “Entities cannot contain entities”. This approach is trying to eliminate the possibility to run database operations on the main thread.
Since we are using a relational database, SQLite, from an object oriented language, Java or Kotlin, for sure you have read something about “object-relational impedance mismatch”. This is actually a fancy way of saying: “Yes, it is difficult getting stuff into and out of the database”. The purpose of an ORM is to solve this issue or at least to make things easier in translating the entities from database to Java or Kotlin classes. Room is also trying to solve this issue.
Relations in Room
In Room there are 3 main approaches that could be used to define the relations between the entities:
1️⃣ @Embedded (nested objects)
A company has 2 offices and let’s say we want to insert these two locations inside of the “Company” table. This thing is possible by using the @Embedded annotation for the two locations. By adding these two locations, Room is trying to create in the “Company” table two columns with the same same. To solve this issue we should add a “prefix” for the headquarter location.
Location.java
public class Location { private double latitude; private double longitude; // getters // setters
Company.java
@Entity(tableName = "Company")
public class Company {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
private int companyId;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "date_updated")
@TypeConverters(DateConverter.class)
private Date itemUpdatedDate;
@Embedded
private Location location;
@Embedded(prefix = "hq_")
private Location headLocation;
2️⃣ @ForeignKey
The company has also employees, so it is a relation 1 to Many. To map this type of relationship we will use the @ForeignKey annotation. Inside of it, we should specify the parent entity, the parent columns, and the child columns.
Also, we could define what should happen “onDelete” or “onUpdate” and we have the next options:
- int CASCADE — A “CASCADE” action propagates the delete or update operation on the parent key to each dependent child key.
- int NO_ACTION //default — When a parent key is modified or deleted from the database, no special action is taken.
- int RESTRICT — The RESTRICT action means that the application is prohibited from deleting (for onDelete()) or modifying (for onUpdate()) a parent key when there exists one or more child keys mapped to it.
- int SET_DEFAULT — The “SET DEFAULT” actions are similar to SET_NULL, except that each of the child key columns is set to contain the columns default value instead of NULL.
Employee.java
@Entity(foreignKeys = @ForeignKey(entity = Company.class,
parentColumns = "id",
childColumns = "company_id",
onDelete = ForeignKey.NO_ACTION))
public class Employee {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
private int employeeId;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "company_id")
private int companyId;
3️⃣ @Relation
A company could also have many departments and let’s say we want to obtain the list of the departments from a company. In order to connect these two entities without adding a @ForeignKey annotation, we could use the @Relation annotation combined with @Embedded.
Department.java
@Entity
public class Department {
@PrimaryKey
private int id;
private int companyId;
private String name;
CompanyAndAllDepartments.java
public class CompanyAndAllDepartments {
@Embedded
public Company company; @Relation(parentColumn = "id", entityColumn = "companyId", entity = Department.class)
public List<Department> departments;
CompanyDepartmentsDao.java
@Dao
public interface CompanyDepartmentsDao { @Transaction
@Query("SELECT * FROM Company WHERE id = :companyId")
CompanyAndAllDepartments loadCompanyAllDepartments(long companyId);
❗Important things to notice❗
✅ If we use the @Relation annotation it is necessary to apply this annotation to a field which is a List or a Set.
✅ If subfields of an embedded field have PrimaryKey annotation, they will not be considered as primary keys in the owner Entity.
✅ Each Entity must declare a primary key unless one of its superclasses declares a primary key. If both an Entity and its superclass define a PrimaryKey, the child’s PrimaryKey definition will override the parent’s PrimaryKey.
✅ By default, the prefix is the empty string
Resources:
- https://developer.android.com/reference/android/arch/persistence/room/ForeignKey.html#constants_1
- https://developer.android.com/reference/android/arch/persistence/room/package-summary
- https://developer.android.com/training/data-storage/room/
Enjoy! Happy coding and feel free to leave a comment if something is not clear or if you have questions. 🙂