Android Room Persistence Library: Relations

Room is an object mapping library that help us, the Android developers, to handle an SQLite database in the Android development.

If you want to learn more about the main components from Room you could check the next two articles:

Android Room Persistence Library: Entity, Dao, Database
Android Room Persistence Library

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 the 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 @Embeddedannotation 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 relation 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 {

    @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 sub fields of an embedded field has 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 super classes declares a primary key. If both an Entity and its super class defines a PrimaryKey, the child’s PrimaryKey definition will override the parent’s PrimaryKey.

✅ By default, prefix is the empty string

Resources:

  1. https://developer.android.com/reference/android/arch/persistence/room/ForeignKey.html#constants_1
  2. https://developer.android.com/reference/android/arch/persistence/room/package-summary
  3. https://developer.android.com/training/data-storage/room/
  4. gifs: https://giphy.com/gifs/minions-8cryeowqTlIs0

Enjoy! Happy coding and feel free to leave a comment if something is not clear or if you have questions. 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s