This article has been translated by Gemini.

I’ve translated the Room Persistence Library | Android Developers documentation into Japanese as a personal note, and I’m sharing it here. (Note: This translation is based on the official documentation as of late May 2017.)
This is a rough translation. Since I wrote this as a personal note, there might be typos, subtle differences in expression, or other errors. If you find any, please let me know, and I’ll correct them as soon as possible. m(__)m
Update on May 30, 2017: Corrected minor typos and wording. Also embedded my presentation slides on Room from CA.apk #3 - Google I/O 2017 Report (held on 2017/05/29) at the bottom of this post.
Room Persistence Library#
The core framework supports SQL content. While the API itself is powerful, it’s low-level and requires significant time and effort to master. Furthermore:
- Raw SQL queries don’t have compile-time validation or analysis. When the data graph changes, you need to manually update the affected SQL queries. This is time-consuming and prone to bugs.
- Converting SQL queries into Java data objects requires a lot of boilerplate code.
Room is a library that provides an abstraction layer over SQLite to solve these two issues.
Room has three major components:#
1. Database#
Used to create a Database Holder. Use the @Database annotation to define the list of entities and include the list of DAOs in the Database class content. Similarly, the Database object is the central access point for the underlying connection.
2. Entity#
Represents a class that holds database columns. A database table is created for each Entity to store its elements. You need to reference the Entity in the entities array of the Database class. All fields in the Entity class are persisted in the database unless the @Ignore annotation is applied to them.
Note:
The Entity class must have either an empty constructor or a constructor that takes at least one field parameter (only if the DAO class can access the fields directly).3. DAO#
Represents a DAO (Data Access Object) class or interface. DAOs are a core component in Room and are responsible for defining the methods used to access the database. A class annotated with @Database must have an abstract method with no arguments that returns a class annotated with @Dao. Room generates the implementation code for this class during compilation.
Important:
By accessing the database through DAO classes instead of query builders or writing queries directly as strings, you can decouple your database configuration from other components. Furthermore, DAOs allow you to easily mock database access when testing.Note:
Since creating a Database class is expensive and there's rarely a need to access multiple instances, you should follow the Singleton design pattern when creating the database object.Entities#
Room creates a database table for an Entity class if it’s annotated with @Entity and referenced within the entities property of a class annotated with @Database.
By default, Room creates a column for every field defined in the Entity class. If there’s a field you don’t want to persist, you can avoid it by adding the @Ignore annotation. To persist a field, Room must be able to access it. You can either make the field public or provide getters and setters. When using getters and setters, remember that Room follows the JavaBeans convention.
Primary Key#
Each Entity must define at least one field as a primary key. Even if only one field is defined, you must add the @PrimaryKey annotation. Similarly, if you want Room to automatically assign IDs to Entities, you can set the autoGenerate property of @PrimaryKey. If an Entity has multiple primary keys, you can use @Entity(primaryKeys = {"", ""}). By default, Room uses the class name as the database table name. If you want to use a different name, you can use the @Entity(tableName="") property.
Note:
Table names in SQLite are case-sensitive.Similar to the tableName property, Room uses field names as column names in the database by default. If you want to use a different column name, you can use @ColumnInfo(name = "").
Indexing and Uniqueness#
Depending on how you access data, you might want to index certain fields to speed up queries. To add an index to an Entity, you can use:
@Entity(indices = {@Index("name"), @Index("last_name", "address")})Sometimes, a specific field or set of fields in the database needs to be unique. To enforce this uniqueness, you can use:
@Entity(indices= { @Index(value = {""}, unique = true) })About Relationships#
Since SQLite is a relational database, you can specify relationships between objects in detail. While most ORM libraries support references between Entity objects, Room explicitly forbids this.
Supplementary Note#
Prohibition of Object References Between Entities
Mapping relationships from a database to corresponding object models is a common practice and works well on the server side, especially for performance with lazy-loaded field access. However, lazy loading doesn’t work well on the client side because the loading process often occurs on the UI thread. Querying information on a disk on the UI thread can cause serious performance issues. The UI thread only has about 16ms for layout updates, calculations, and drawing for an activity. So, even if a query only takes 5ms, the application might run out of time to draw the frame, resulting in noticeable “jank” for the user. Even worse, if multiple processes are running concurrently or the device is performing disk-heavy tasks, the query time might exceed 16ms. However, without lazy loading, an application might load more data than necessary, leading to memory consumption issues.
ORM libraries leave these discussions to developers, so developers use ORMs in app development in whatever way they think is best. Unfortunately, developers commonly fall into the trap of using lazy-loaded models between both the application and the UI.
For example, consider a UI that loads multiple Book objects, each having an Author object. Initially, the query design using lazy loading might provide a getAuthor() method within the Book object that returns an Author object. With this design, the first time getAuthor() is executed, it triggers a database query. As time goes by and requirements change, the UI might need to display the author’s name in addition to what it already displays. It’s very easy to implement this in the UI by adding the getAuthor().getName() method:
authorNameTextView.setText(user.getAuthor().getName());Such a simple, seemingly harmless change unknowingly causes a query to the Author table to be performed on the main thread.
For these reasons, Room forbids object references between Entities and instead is designed so that developers must explicitly request only the data the app actually needs.
While direct relations aren’t allowed, Room permits defining ForeignKey constraints between Entities.
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))ForeignKeys are very powerful and allow you to specify what happens when a referenced Entity is updated. For example, you can tell SQLite to delete all of a user’s Book information if the User object is deleted from the DB.
Details: https://sqlite.org/lang_conflict.html
Nested Objects#
Sometimes, even if an object holds multiple fields, you might want to represent that Entity or POJO class object as a single unit of database logic. In such cases, you can use the @Embedded annotation to embed one Entity class into another.
For example, a User class can contain a field of type Address (where the Address class holds multiple fields like street, city, state, and post code).
class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code")
public int postCode;
}
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}The database table will be named “User” and will have columns for id, firstName, street, state, city, and post_code.
Note: Embedded fields can also contain other embedded fields.
If an Entity holds multiple embedded fields of the same type, you can add a prefix to maintain the uniqueness of each column name.
DAO (Data Access Objects)#
The main component of Room is the Dao class. DAOs provide a clean abstraction for database access.
Insert#
@Insert public void insertBothUsers(User user1, User user2);
@Insert public void insertUsersAndFriends(User user, List<Friend> friends);If an @Insert method receives a single argument, it can return a long value representing the new rowId. If the arguments are an array or a collection, the return value can be long[] or List.
https://www.sqlite.org/rowidtable.html
Update#
@Update public void updateUsers(User… Users);Although usually not necessary, it can return an int representing the number of updated rows.
Delete#
@Delete public void deleteUsers(User… Users);Although usually not necessary, it can return an int representing the number of deleted rows.
Methods Using the @Query Annotation#
This annotation is used for read/write operations on the database. Each @Query method is validated during compilation. If there are any issues, a compilation error is triggered. Similarly, Room validates the return values of query methods. If a field name in the return object doesn’t match the column name in the query result, it alerts you in one of the following ways:
- A warning if only some field names don’t match.
- An error if all field names don’t match.
Room also reports errors during compilation if a query contains syntax errors or if a table isn’t defined in the database.
Passing Parameters to Queries#
When accessing a database, you often pass parameters to a query to perform filtering. In such cases, use method parameters within the Room annotation.
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}When the query is processed during compilation, Room checks if the :minAge bind parameter name matches the method argument name. Room performs consistency checks using parameter names. If there’s a mismatch, an error occurs.
You can also pass multiple arguments or reference a single argument multiple times.
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
@Query("SELECT * FROM user WHERE first_name LIKE :search "
+ "OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}Returning Only a Subset of Columns#
Often, you only need a few fields from an Entity. For example, if you’re only displaying the user’s first and last names in the UI, you don’t need to read all the detailed information about the user. In such cases, reading information only from the necessary columns can reduce resource allocation for variables and lead to faster database queries.
In Room, you can specify any Java object as a return value, as long as the list of columns selected in the query can be mapped to that return object.
public class NameTuple {
@ColumnInfo(name="first_name")
public String firstName;
@ColumnInfo(name="last_name")
public String lastName;
}@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}A warning is displayed if there are too many columns, if a specified column can’t be mapped to the specified return class, or if it doesn’t exist.
NOTE:
In such cases, you can use the @Embedded annotation.
Using Collections as Arguments#
Sometimes you only know the exact number of arguments at runtime. For example, if you want to get information about several regions. Room understands if an argument is a Collection, and for Collections, it automatically expands the query to provide the exact number of arguments to the method at runtime.
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}Observable Queries#
Sometimes you want the application’s UI to update automatically when data is updated. To achieve this, make the return value of the Room Dao method a LiveData type. Room generates all the code necessary to update the LiveData when the database is updated.
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}(!! Not quite sure how to translate this part yet... !!)
In version 1.0, does Room use the list of tables accessed in a query to determine if the LiveData object needs updating?RxJava#
Similarly, in Room, you can specify RxJava2’s Publisher and Flowable objects as return values for queries.
By adding the dependency:
android.arch.persistence.room:rxjava2you can specify Publisher or Flowable as the return value.
Direct Access to Cursor#
If your application logic requires direct access to rows returned from the database, you can specify a Cursor object as the return value.
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}Note:
Using the Cursor API is not recommended because there's no guarantee that rows actually exist or what values they contain. It should only be used if such an implementation already exists in your app and refactoring isn't easy.Multi-table Queries#
Some queries require access to multiple tables to calculate a result. Room allows you to write any query statement, so you can also include JOIN statements. If you specify RxJava2’s Flowable, Publisher, or LiveData as the return value, Room monitors all tables referenced in the query for error detection.
TypeConverter#
(Author’s Note: A feature similar to Moshi’s TypeAdapter.)
You can use it by adding the @TypeConverter annotation to a method and adding the @TypeConverters annotation to various locations like the DB class, Dao methods, or Entity classes.
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}Database Migrations#
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER");
}
};Create a Migration class. At runtime, Room executes the migrate() methods of the Migration classes in order. If a necessary migration process is missing, Room rebuilds the DB, so data will be lost.
After the migration is complete, Room checks the schema to verify if the migration was performed correctly. If it failed, an Exception occurs.
Testing Migrations#
You can test migration processes in advance by exporting the database schema.
Exporting Schemas#
Room can output schema information to a JSON file during compilation. To export the schema, add the following to build.gradle:
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}By keeping the exported JSON file under version control, Room can generate a database with an old schema for migration tests in the future.
Add the dependency:
android.arch.persistence.room:testingSpecify the path where schemas are managed as an asset folder:
android {
…
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}The testing package contains a MigrationTestHelper class. By executing its runMigrationsAndValidate() method, the helper class automatically validates schema changes. You need to validate data changes yourself.
Final Note#
The above is the full text of my Japanese translation of the official Room documentation. Since I wrote this as a personal note, I hope you find it helpful to pick out parts you’re interested in for reference.
Also, I gave a presentation about Room at CA.apk on May 29, 2017. If you’re looking for more information on Room, please take a look at those slides as well.