Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Room - SQLite Object Mapping Library

Room - SQLite Object Mapping Library

Room is a object mapping library for SQLite, made and announced at Google I/O by Google, Inc. It fully supports SQLite and focuses on what SQLite can do while making it dramatically easy to map database queries and objects.

* Room Persistence Library | Android Developers
https://developer.android.com/topic/libraries/architecture/room.html

* SQLiteDatabaseのINSERT/UPDATE文でコンフリクト対応する話 - Qiita
http://qiita.com/Yuki_312/items/9267dc31d2720fe36505

* Room Persistence Libraryを訳してみた(※2017年5月末時点) - へいへいブログ📔
http://shaunkawano.hatenablog.com/entry/2017/05/29/202111

Shohei Kawano

May 29, 2017
Tweet

More Decks by Shohei Kawano

Other Decks in Programming

Transcript

  1. Data Persistence for UX • Enabling Offline mode • Preventing

    redundant API calls for unchangeable data • Loading data from local storage is much faster than loading data from network => Enabling faster data loading
  2. Persistent Storage in Android • Content Providers: Publicly sharing data

    • Shared Preferences: key-value data • SQLite: Structured data
  3. Persistent Storage in Android • Content Providers: Publicly sharing data

    • Shared Preferences: key-value data • SQLite: Structured data
  4. Persistent Storage in Android • Content Providers: Publicly sharing data

    • Shared Preferences: key-value data • SQLite: Structured data → Android provides Core Framework API for SQLite
  5. Persistent Storage in Android • Content Providers: Publicly sharing data

    • Shared Preferences: key-value data • SQLite: Structured data → Android provides Core Framework API for SQLite; works for API level 1~ Let’s read the documentation!
  6. • No compile-time analysis for SQLite statements, so hard to

    debug, prone to bug. • A lot of boilerplate codes required for converting SQL queries into Application- specific data objects.
  7. What is Room? - 1 • Introduced in Google I/O

    2017 • One of the Architecture Components
 (a.k.a. arch) • Object mapping library for persisting data with SQLite
  8. What is Room? - 2 • Fully supports SQLite; focuses

    on what SQLite can do. • Provide compile-time analysis for SQL queries for better debugging experience and less bugs • Provide Observability and testability
  9. What is Room? - 3 • Generate boilerplate codes and

    implementation by declaring interface with annotations (Similar to Retrofit or Dagger) • No direct “R”elationship between entities; No object references between @Entities • Use @Embedded or @ForeignKey to save or access to minimal data
  10. Rest of the Content • Basics of Room • Reading

    Some of the generated source codes • TypeConverter and observable queries • Conclusion • Extra
  11. Not to cover in this talk • About Migration •

    How to write tests using Room • Other in-depth content
  12. @Database(entities = arrayOf(User::class), version = 1) abstract class AppDatabase :

    RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt
  13. @Entity(tableName = "follower") class User( @PrimaryKey val uid: Int, val

    firstName: String, val lastName: String) User.kt
  14. @Entity(tableName = "follower") class User( @PrimaryKey val uid: Int, @ColumnInfo(name

    = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String) User.kt
  15. User.kt @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int,


    At least one field annotated with @PrimaryKey is required.
  16. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, ….

    val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val province: String, val city: String )
  17. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, ….

    @Embedded val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val province: String, val city: String )
  18. @Embedded User table VJE QPTU@DPEF TUSFFU QSPWJODF DJUZ  

    “Maruyamacho …“ l5PLZPz l4IJCVZBz 6TFSLU "EESFTTLU "EESFTTLU "EESFTTLU "EESFTTLU
  19. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, ….

    @Embedded val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )
  20. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, ….

    @Embedded val primaryAddress: Address @Embedded val secondaryAddress: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )
  21. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, ….

    @Embedded val primaryAddress: Address @Embedded (prefix = “secondary_”) val secondaryAddress: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )
  22. @Embedded User Table VJE QPTU@DPE F TUSFFU QSPWJODF DJUZ TFDPOEB

    SZ@QPTU@ DPEF TFDPOEB SZ@TUSFFU ʜ 1 1500043 “Maruyam acho…“ “Tokyo” “Shibuya” 1500043 “Kamiyam acho…”
  23. @Entity( primaryKeys = arrayOf("first_name", "last_name"), tableName = "follower", indices =

    arrayOf(Index("last_name"), Index(value = "first_name", unique = true)), inheritSuperIndices = false ) @Entity Parameters
  24. @ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns

    = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)
  25. @ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns

    = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)
  26. @ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns

    = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)
  27. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  28. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  29. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  30. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  31. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  32. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  33. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  34. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User> Very similar to Retrofit’s service interfaces
  35. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User>
  36. @Database(entities = arrayOf(User::class), version = 1) abstract class AppDatabase :

    RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt
  37. UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List<User>) @Update

    fun update(users: List<User>) @Delete fun delete(user: User) @Query("SELECT * FROM USER") fun getAll(): List<User>
  38. UserDao_Impl.java @Override public List<User> getAll() { final String _sql =

    "SELECT * FROM USER"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); final Cursor _cursor = __db.query(_statement); try { final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); …
  39. UserDao_Impl.java @Override public List<User> getAll() { final String _sql =

    "SELECT * FROM USER"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); final Cursor _cursor = __db.query(_statement); try { final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); …
  40. UserDao_Impl.java final List<User> _result = new ArrayList<User>(_cursor.getCount()); while(_cursor.moveToNext()) { final

    User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }
  41. UserDao_Impl.java final List<User> _result = new ArrayList<User>(_cursor.getCount()); while(_cursor.moveToNext()) { final

    User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }
  42. UserDao_Impl.java final List<User> _result = new ArrayList<User>(_cursor.getCount()); while(_cursor.moveToNext()) { final

    User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }
  43. UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun

    getAll(): List<User> @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List<User>
  44. @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")

    public User[] loadAllUsersBetweenAges(int minAge, int maxAge); UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List<User> @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List<User>
  45. @Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR

    last_name LIKE :search") fun findUserWithName(search: String): List<User> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List<User> @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List<User>
  46. UserDao.kt @Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.ROLLBACK) fun insertAll(users:

    List<User>) @Update(onConflict = OnConflictStrategy.REPLACE) fun updateUsers(users: List<User>)
  47. @Retention(SOURCE) … public @interface OnConflictStrategy { int REPLACE = 1;

    int ROLLBACK = 2; int ABORT = 3; int FAIL = 4; int IGNORE = 5; } OnConflictStrategy.java
  48. @Retention(SOURCE) … public @interface OnConflictStrategy { int REPLACE = 1;

    int ROLLBACK = 2; int ABORT = 3; int FAIL = 4; int IGNORE = 5; } OnConflictStrategy.java https://sqlite.org/lang_conflict.html Based on SQLite Conflict Algorithm
  49. @Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase

    : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt
  50. @Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase

    : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt
  51. @Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase

    : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt
  52. @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int, @ColumnInfo(name

    = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @JvmField val birthday: LocalDateTime ) User.kt
  53. class LocalDateTimeConverter { @TypeConverter fun fromTimestamp(value: Long?): LocalDateTime? { return

    if (value == null) null else LocalDateTime.ofEpochSecond(value, 0, ZoneOffset.UTC) } @TypeConverter fun dateToTimestamp(date: LocalDateTime?): Long? { return date?.toEpochSecond(ZoneOffset.UTC) …. LocalDateTimeConverter.kt
  54. class LocalDateTimeConverter { @TypeConverter fun fromTimestamp(value: Long?): LocalDateTime? { return

    if (value == null) null else LocalDateTime.ofEpochSecond(value, 0, ZoneOffset.UTC) } @TypeConverter fun dateToTimestamp(date: LocalDateTime?): Long? { return date?.toEpochSecond(ZoneOffset.UTC) …. LocalDateTimeConverter.kt
  55. private final LocalDateTimeConverter __localDateTimeConverter = new LocalDateTimeConverter(); ... final LocalDateTime

    _tmpBirthday; final Long _tmp; ... _tmpBirthday = __localDateTimeConverter.fromTimestamp(_tmp); UserDao_Impl.java
  56. @Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR

    last_name LIKE :search") fun findUserWithName(search: String): List<User> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List<User>
  57. @Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR

    last_name LIKE :search") fun findUserWithName(search: String): LiveData<List<User>> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flowable<List<User>>
  58. @Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR

    last_name LIKE :search") fun findUserWithName(search: String): LiveData<List<User>> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flowable<List<User>>
  59. Conclusion • Room provides simple and customizable APIs with raw

    SQL queries to solve current problems on Framework APIs • Room forbids direct references between entities and forces to access/fetch as minimal data as possible • Room supports LiveData and Flowable integration for observability
  60. Extra • You may not need Room if current DB

    or ORM library works better for you • Compared to other Architecture Components, Room is more stable and ready to be used • Room currently does not support query builders - “That’s a Android Studio’s job and we are working on it”