Room is part of the database framework in Google’s JetPack component library

Analysis of the

  1. Realm
    1. High performance, 10 times faster than SQLite
    2. Support RxJava/Kotlin
    3. But without nested classes and requiring fields to specify default values, I find nested data classes indispensable
    4. Custom database engine, so it will require the import of JNI library, resulting in apK volume explosion (multiple architecture platform integration more than 5MB)
    5. Function design is complicated
    6. The official graphics tools are relatively crude but up-to-date
    7. Cross-platform, Android/iOS/Mac/Windows
    8. Support for listening databases
  2. DBFlow
    1. The main use of functions to operate the database, high learning costs
    2. Native support for database encryption
    3. Support for listening databases
    4. Support coroutine /Kotlin/RxJava
    5. Out of favor, especially domestically
  3. GreenDao
    1. Relatively outdated, complex configuration
    2. Features such as listening tables /Kotlin/ coroutines are not supported
    3. No longer actively maintain, the official is currently actively maintain its other open source database ObjectBox
  4. ObjectBox 2. The world’s fastest embedded database 2. No support for nested objects 3. Small size (minimum compression to increase volume 1MB) 4. Simple and elegant function design 5. Support DSL 6. Support to listen to the database 7. 9. JSON file automatically generated according to the configuration migration cross-platform, Android/iOS/Mac/Windows/Go
  5. ROOM
    1. The mainstream
    2. SQL statement support
    3. Manipulating a database requires writing abstract functions
    4. Official maintenance of the database framework in JetPack components
    5. Listening database
    6. Support for nested objects
    7. Support for Kotlin coroutines /RxJava
    8. SQL statement highlighting and compile-time checking (with AndroidStudio support)
    9. Using SQLite facilitates the transfer of database files across multiple platforms (for example, some contact information is a SQLite file)
    10. Because SQLite can encrypt database through third-party framework (ROOM native does not support)
    11. This works with AndroidStudio’s built-in database view window

Conclusion:

I recommend ROOM for its size and ease of use

I usually project development necessary framework

  1. Network Request Net
  2. List (including StateLayout) BRV
  3. The default page StateLayout
  4. JSON and long text log printing tool LogCat
  5. Supports asynchronous and global customization of the toast tool Tooltip
  6. Develop the debugging window tool DebugKit
  7. One line of code creates the transparent StatusBar StatusBar

features

  1. SQL statements are highlighted
  2. Simple introduction
  3. powerful
  4. Database listening
  5. Support Kotlin coroutine /RxJava/Guava

Rely on

dependencies {
  def room_version = "2.2.0 - rc01"

  implementation "androidx.room:room-runtime:$room_version"
  annotationProcessor "androidx.room:room-compiler:$room_version" 
  // Kotlin uses kapt instead of annotationProcessor

  // Optional - Kotlin extension and coroutine support
  implementation "androidx.room:room-ktx:$room_version"

  // Optional - RxJava support
  implementation "androidx.room:room-rxjava2:$room_version"

  // Optional - Guava support, including Optional and ListenableFuture
  implementation "androidx.room:room-guava:$room_version"

  // Test help
  testImplementation "androidx.room:room-testing:$room_version"
}
Copy the code

Gradle configuration

android {
  ...
    defaultConfig {
      ...
        javaCompileOptions {
          annotationProcessorOptions {
            arguments = [
              "room.schemaLocation":"$projectDir/schemas".toString(),
              "room.incremental":"true"."room.expandProjection":"true"]}}}}Copy the code
  • room.expandProjection: Rewrites SQL queries based on function return types when using star projections
  • room.schemaLocation: Displays the database summary. You can view the field information, version number, and database creation statement
  • room.incremental: Enables the Gradle incremental comment processor

use

ROOM creates all the registered table structures as soon as the database object is created

  1. Creating a database
  2. Creating an Operation Interface
  3. Create a data class: typically a data class derived from a JSON reverse sequence
  4. use

Creating a database

@Database(entities = [Book::class], version = 1)
abstract class SQLDatabase : RoomDatabase() {
    abstract fun book(a): BookDao
}
Copy the code

Creating an Operation Interface

@Dao
interface BookDao {

    @Query("select * from Book where")
    fun qeuryAll(a): List<Book>

    @Insert
    fun insert(vararg book: Book): List<Long>

    @Delete
    fun delete(book: Book): Int

    @Update
    fun update(book: Book): Int

}
Copy the code

Creating a data class

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var number: Long = 0.var title:String
)
Copy the code

use

val db = Room.databaseBuilder(this, SQLDatabase::class.java."drake").build(a)// drake is the database file name

val book = Book("Alive")
db.book().insert(book)

val books = db.user().qeuryAll()
Copy the code

annotations

Entity

@Entity

The modifier class acts as a data table whose name is case insensitive

public @interface Entity {
    /** * table name, default class name table name */
    String tableName(a) default "";

    /** * Index example:@Entity(indices = {@Index("name"), @Index("last_name", "address")})
     */
    Index[] indices() default {};

    /** * whether to inherit the parent index */
    boolean inheritSuperIndices(a) default false;

    /** * joins the primary key */
    String[] primaryKeys() default {};

    /** * foreign key array */
    ForeignKey[] foreignKeys() default {};

    /** * ignores the field array */
    String[] ignoredColumns() default {};
}
Copy the code

ROOM requires public access for each database serialized field

Index

@Index

public @interface Index {
    /** * specifies the field name of the index */
    String[] value();

    ** * index fieldName * index_${tableName}_${fieldName} example: index_Foo_bar_baz */
    String name(a) default "";

    /** ** only */
    boolean unique(a) default false;
}
Copy the code

Ignore

@Ignore

Fields decorated by this annotation are not counted in the table structure

Database

public @interface Database {
    /** * specifies that the data table is created when the database is initializedClass<? >[] entities();/** * specifies which views the database contains */Class<? >[] views()default {};

    /** * The current database version */
    int version(a);

    /** * Whether database profiles are allowed anywhere. Default is true. Gradle 'room. SchemaLocation' is required to be configured */
    boolean exportSchema(a) default true;
}
Copy the code

PrimaryKey

@PrimaryKey

Each database requires at least one primary key field, even if there is only one field in the data table

boolean autoGenerate() default false; // The primary key grows automaticallyCopy the code

If the primary key is automatically generated, it must be of type Long or Int.

ForeignKey

@ForeignKey

public @interface ForeignKey {
  // The table entity that references the foreign key
  Class entity(a);
  
  // The foreign key column to reference
  String[] parentColumns();
  
  // The column to be associated
  String[] childColumns();
  
  // The action that is performed when the parent entity (the associated foreign key table) is deleted from the database
  @Action int onDelete(a) default NO_ACTION;
  
  // The operation performed when the superclass entity (the associated foreign key table) is updated
  @Action int onUpdate(a) default NO_ACTION;
  
  // Whether the foreign key constraint should be deferred until the transaction completes
  boolean deferred(a) default false;
  
  // Operations defined for onDelete, onUpdate
  int NO_ACTION = 1; / / no action
  int RESTRICT = 2; // Do not delete the parent key if there are child keys
  int SET_NULL = 3; // Child table deletion causes the parent key to be set to NULL
  int SET_DEFAULT = 4; // Child table deletion causes the parent key to be set to the default value
  int CASCADE = 5; // Delete all key entries in the child table
  
  @IntDef({NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE})
  @interface Action {
    }
}
Copy the code

The sample

@Entity
@ForeignKey(entity = Person::class, parentColumns = ["personId"],childColumns = ["bookId"], onDelete = ForeignKey.RESTRICT )
data class Book(
    @PrimaryKey(autoGenerate = true)
    var bookId: Int = 0.@ColumnInfo(defaultValue = "12") var title: String = "A Song of Ice and Fire"
)
Copy the code

ColumnInfo

Modify fields as columns (fields) in the database

public @interface ColumnInfo {
    /** * the column name, which defaults to the currently decorated field name */
    String name(a) default INHERIT_FIELD_NAME;

    /** * specifies that the current field is of type Affinity. */ is generally not used
    @SQLiteTypeAffinity int typeAffinity(a) default UNDEFINED;
  
  	// The following types
    int UNDEFINED = 1;
    int TEXT = 2;
    int INTEGER = 3;
    int REAL = 4; //
    int BLOB = 5;

    /** * This field is the index */
    boolean index(a) default false;

    /** * specifies the order in which the columns are arranged when the table is built */
    @Collate int collate(a) default UNSPECIFIED;
  
    int UNSPECIFIED = 1; // Default value, similar to BINARY
    int BINARY = 2; // Case sensitive
    int NOCASE = 3; // case insensitive
    int RTRIM = 4; // Case sensitive arrangement, omit trailing Spaces
		@RequiresApi(21)
    int LOCALIZED = 5; // Use the current system default sequence
    @RequiresApi(21)
    int UNICODE = 6; / / unicode order

    /** * The default value of the current column, if the default value changes to require processing database migration, this parameter supports SQL statement function */
    String defaultValue(a) default VALUE_UNSPECIFIED;
}
Copy the code
  1. The main parameters used are onlyindex/name
  2. Not only fields of the Entity class can be modified by this annotation, but non-entity classes can also be modified by this annotation (for example, POJO classes used to expand projections; type projections are covered later).

Index

@Index

Increase query speed

// The name of the field to be indexed
String[] value();

// Index name
String name(a) default "";

// indicates that the field is unique in the table and cannot be repeated
boolean unique(a) default false;
Copy the code

RawQuery

@RawQuery

This annotation is used to modify Dao functions with the SupportSQLiteQuery parameter for raw queries (the compiler does not validate SQL statements), typically using @Query

 interface RawDao {
    @RawQuery
    fun queryBook(query:SupportSQLiteQuery): Book
 }

va; book = rawDao.queryBook(SimpleSQLiteQuery("SELECT * FROM song ORDER BY name DESC"));
Copy the code

To return observable objects such as Flow, specify the annotation parameter observedEntities

@RawQuery(observedEntities = [Book::class])
fun query(query:SupportSQLiteQuery): Flow<MutableList<Book>>
Copy the code

Embedded

@Embedded

If a data table entity has a field that belongs to another object, that field can be modified with this annotation to make the external class contain all of the fields of that class (in the data table).

This external class should not be considered an Entity modified table structure

String prefix(a) default  ""; 
// prefix, which is added to all field names in the data table
Copy the code

Relation

@Relation

Earlier, this annotation could only modify collection fields, but now it can modify any type.

The following demonstrates creating a one-to-many

Create a book

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0.var title:String = "drake"
)
Copy the code

Create a person

@Entity
data class Person(
    @PrimaryKey(autoGenerate = true) var id: Int = 0.var name: String
)
Copy the code

Create a user

data class User(
    @Embedded var person: Person,

    @Relation(entity = Book::class, parentColumn = "id", entityColumn = "id")
    var book: Book List
      
        = List
      
)
Copy the code
  1. Entity parameter Generally, this parameter is not required. It can be inferred from the return value type. You can use this parameter if you want to define the target entity
  2. User is not an Entity
  3. User must contain all the fields of Person, so Embedded annotations are recommended
  4. parentColumnCorresponding to the field in User,entityColumnCorresponding to fields in Book (i.e., the “many” data table in one-to-many)

You can then freely insert Person or Book and return User when querying

@Dao
interface UserDao {
    @Query("select * from person")
    fun find(a): List<User>
}
Copy the code

You can see that the query SQL statement queries the Person table, but the function return type is List

. The User contains both Peron and the Book corresponding to Person.

The so-called corresponding principle namely parentColumn/entityColumn these two properties, parentColumn said

Returns the specified column directly, by default by type

data class User(
    @Embedded var person: Person,

    @Relation(entity = Book::class, parentColumn = "id", entityColumn = "id")
    var book: Book List
      
        = List
      
)
Copy the code

Bridge table

Define a separate data table to represent the relationship between two data tables

Create a bridge table

@Entity(primaryKeys = ["personId"."bookId"])
data class LibraryRelation(
    var personId: Int.var bookId: Int
)
Copy the code
  1. Bridging tables require the same primary key

The bridge table is specified by the parameter associateBy

data class User(
    @Embedded var person: Person,
    @Relation(
        entity = Book::class,
        parentColumn = "personId",
        entityColumn = "bookId",
        associateBy = Junction(BookCaseRef::class, parentColumn = "pId", entityColumn = "bId")
    )
    var book: List<Book>
)
Copy the code
  1. In the JunctionparentColumn/entityColumnThe default value is the argument of the same name in Relation. The meaning is the name of the field in the bridge table

Complete many-to-many queries

data class User(
    @Embedded var person: Person,
    @Relation(
        entity = Book::class,
        parentColumn = "personId",
        entityColumn = "bookId",
        associateBy = Junction(BookCaseRef::class, parentColumn = "pId", entityColumn = "bId")
    )
    var book: List<Book>
)

data class BookCase(
    @Embedded var book: Book,
    @Relation(
        entity = Person::class,
        parentColumn = "bookId",
        entityColumn = "personId",
        associateBy = Junction(BookCaseRef::class, parentColumn = "bId", entityColumn = "pId")
    )
    var person: List<Person>
)
Copy the code

Transaction

The Dao abstract class allows you to create a function annotated with @Transaction in which the database operations are in a Transaction

Typically, the function runTransaction is used

@Dao
abstract class UserDao {
    @Insert
    abstract fun insertPerson(person: Person): Long

    @Query("select * from Person")
    abstract fun findUser(a): List<User>

    @Delete
    abstract fun delete(p: Person)

    @Transaction
    open fun multiOperation(deleteId: Int) {
        insertPerson(Person(deleteId, "nathan"))
        insertPerson(Person(deleteId, "nathan")) // The transaction failed due to a duplicate primary key conflict}}Copy the code
  1. Insert/Delete/Update modified functions are themselves in transactions
  2. ROOM allows only one transaction to run, with other transactions queued
  3. @tranAction requires that the decorated function cannot befinal/private/abstract, but can be abstract if the function also contains @query
  4. Query If the Query containing the Relation annotation has more than one Query, this parameter is used@TransactionMultiple queries will be placed in a transaction to avoid other transactions

DML

  1. Add, delete, modify, and search are all subject to the primary key, that is, other attributes of the data can not correspond to the records in the data table or can be deleted according to the primary key
  2. DML in ROOM is all performed by annotated abstract functions
  3. DML functions can return Long for Insert and Int for other Update/Delete functions. Or all return to Unit.
  4. When the argument is mutable, the return value should also be of mutable type; otherwise, only the value of the first record is returned
  5. Mutable types include List/MutableList/Array
  6. When performing DML operations on multiple data bodies (such as inserting multiple users), any one that does not match will discard the commit altogether
  7. All DML annotations except Transaction are required to be abstract/public/rewritable

All DML operations require the definition of abstract functions in an interface decorated with @DAO

@Dao
interface BookDao {

    @Query("select * from Book")
    fun find(a): Flow<Book>

    @Insert
     fun insert(vararg book: Book): List<Long>

    @Delete
    fun delete(book: Book): Int

    @Update
    fun update(book: Book): Int

}
Copy the code
  1. Daos can be abstract classes or interfaces

Insert

@Insert
fun insert(book: Book): Long

@Insert
fun insert(vararg book: Book): List<Long>
Copy the code

@Insert

/** * how to handle conflicts when inserting columns * Use {@linkOnConflictStrategy#ABORT} (default) Rollback transaction * Use {@linkOnConflictStrategy#REPLACE} REPLACE existing columns * Use {@linkOnConflictStrategy#IGNORE} preserves existing columns */
@OnConflictStrategy
int onConflict() default OnConflictStrategy.ABORT;
Copy the code
  1. The return value of the modified function must be of type Long: the primary key of the inserted record
  2. Automatically generate the primary key for the primary key is Long or Int type, value must be 0 will automatically generated at the same time, if the specified primary key and repeat sell SQLiteConstraintException manually

Delete

@Delete

The return type of the modified function must be Int: to remove the row index, starting at 1

Update

@Update

Update rows based on primary key matches

The return value can be Int to update the column index

Query

@Query

This annotation takes a single string argument, which is part of an SQL query and is highlighted by the compiler’s validation rules and code and (very powerful here) auto-complete.

The compiler verifies that the return value matches the query column

To reference function parameters use :{parameter name}

@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);
}
Copy the code
  1. When the SQL statement is written, the data table is case-sensitive against the class name. Otherwise, the table name in the SQL statement cannot be taken into account when the class name is renamed

Field mapping

You may only need a few fields of the table, so you can create a new object to receive partial field results of the query

The user data table contains many fields

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);
}
Copy the code
  • NameTuple objects can be decorated with non-entity annotations
  • Name The name of the field in the data table (or annotated with ColumnInfo)

Query parameters

Query parameters can use collections

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
Copy the code

The query results

  • Entity object: The first object in the query collection
  • Array: An empty array with no result
  • Set: An empty set with no results

Returns NULL if the entity object is not queried, and returns empty List if the collection is not queried.

Can be viewed

Query functions can register observers with the following return types

  • LiveData

    • Initialize the
    • delete
    • update
    • insert
  • Flowable

    • insert
    • update

Observers are notified when rows in the data table in the query are updated.

Execute SQL statement

The @Query annotation can execute virtually any SQL statement

@Query("delete from book where bookId = :bookId")
fun deleteFromId(bookId: Int): Int // Delete rows by Id, return Int to affect the number of rows in the table
Copy the code

# # # view

@DatabaseView

The database view represents the creation of a virtual table whose table structure may be partial columns of other tables. The main purpose is to reuse data tables

@DatabaseView("SELECT user.id, user.name, user.departmentId," +
        "department.name AS departmentName FROM user " +
        "INNER JOIN department ON user.departmentId = department.id")
data class UserDetail(
    var id: Long.varname: String? .var departmentId: Long.var departmentName: String?
)
Copy the code

The field name is automatically mapped to the field name of the entity class

Register the view to the database before the view can be created, and then query the view like a table; The View array is all the view bytecode

@Database(entities = [User::class], views = [MovieView::class], version = 1)
abstract class SQLDatabase : RoomDatabase() {
    abstract fun user(a): UserDao
}
Copy the code

Annotation parameters

public @interface DatabaseView {
    /** * query statement */
    String value(a) default "";

    /** * View name, default is class name */
    String viewName(a) default "";
}
Copy the code

ROOM

Create database access objects

The singleton pattern should be followed and multiple database instance objects should not be accessed

static <T extends RoomDatabase> Builder<T>	databaseBuilder(Context context, Class
       
         klass, String name)
       
// Create a serialized database

static <T extends RoomDatabase> Builder<T>	inMemoryDatabaseBuilder(Context context, Class<T> klass)
// Create a database in memory that will be cleared after application destruction
Copy the code

ROOM does not allow access to the database from the main thread by default, unless the function allowMainThreadQueries is used, but locking the UI is not recommended, especially when querying database contents in list swiping.

RoomDatabase

public abstract void clearAllTables(a)
// Clear all rows in the table

public boolean	isOpen(a)
// Return false if the database connection is already initialized

public void close(a)
// Close the database (if already open)

public InvalidationTracker	getInvalidationTracker(a)
public Returns the invalidation tracker for this database.

public SupportSQLiteOpenHelper	getOpenHelper(a)
// Return the SQLiteOpenHelper object that uses the database

public Executor getQueryExecutor(a)
public Executor getTransactionExecutor(a)
  
public boolean	inTransaction(a)
Return true if the current thread is in a transaction

public Cursor query(String query, Object[] args)
// A shortcut function to query the database with parameters
Copy the code

The transaction

All database operations in interface callbacks are transactions and are rolled back if they fail

public void runInTransaction(@NonNull Runnable body)

public <V> V runInTransaction(@NonNull Callable<V> body)
Copy the code

RoomDatabase constructor

Roomdatabase.builder This constructor is responsible for building a database instance object

public Builder<T>	addMigrations(Migration... migrations)
// Add migration

public Builder<T>	allowMainThreadQueries(a)
// allow mainthread queries

public Builder<T>	createFromAsset(String databaseFilePath)
Create and open a pre-packaged database in the 'assets/' directory

public Builder<T>	createFromFile(File databaseFile)
// Configure room to create and open a pre-packaged database

public Builder<T>	fallbackToDestructiveMigration(a)
// If no migration is found, a destructive database rebuild is allowed

public Builder<T>	fallbackToDestructiveMigrationFrom(int. startVersions)
// Only the specified start version is allowed to destructively rebuild the database
  
public Builder<T>	fallbackToDestructiveMigrationOnDowngrade(a)
// Destructive migrations are allowed if migrations are not available when you degrade an older version

public T	build(a)
// Create database
Copy the code

Functions that are not commonly used

public Builder<T>	enableMultiInstanceInvalidation(a)
// Set the notification and synchronization of invalid tables in one database instance. Both database instances must be enabled to be valid.
// This does not apply to in-memory databases, just database instances for different database files
// It is disabled by default

public Builder<T>	openHelperFactory(SupportSQLiteOpenHelper.Factory factory)
// Set up the database factory

public Builder<T>	setQueryExecutor(Executor executor)
// Set the thread executor for asynchronous queries. Use coroutines instead of this function

public Builder<T>	setTransactionExecutor(Executor executor)
// Sets the thread executor for asynchronous transactions
Copy the code

The log

Set the logging mode of SQLite

Builder<T>	setJournalMode(roomDatabase.journalMode JournalMode) // Sets the JournalModeCopy the code

JournalMode

  • No log TRUNCATE
  • WRITE_AHEAD_LOGGING Outputs logs
  • AUTOMATIC default behavior, low RAM or lower than API16 without logging

The life cycle

Builder<T>	addCallback(RoomDatabase.Callback callback)
Copy the code

RoomDatabase.Callback

public void	onCreate(SupportSQLiteDatabase db)
// When the database is first created

public void	onDestructiveMigration(SupportSQLiteDatabase db)
// After a destructive migration

public void	onOpen(SupportSQLiteDatabase db)
// Open the database
Copy the code

Type conversion

By default, only basic types and their boxing classes are allowed in queries. If we want to use other types as query criteria and field types, we need to define type converters.

You can convert content between custom objects and database serialization using @Typeconverter

class  DateConvert {

    @TypeConverter
    fun fromDate(date: Date): Long {
        return date.time
    }

    @TypeConverter
    fun toDate(date: Long): Date {
        return Date(date)
    }
}
Copy the code

TypeConverters can be decorated

  1. Database (@database modifier)
  2. Data body (@entity modifier)
  3. Field properties of the data body, (parameters are not supported)
  4. The official documentation says you can decorate the Dao but I’ll get an error when I try

The scope varies depending on the modifier

In a modified database, Date goes through the converter throughout the database operation

@Database(entities = {User.java}, version = 1)
@TypeConverters({DateConvert.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao(a);
}
Copy the code

DQL

ROOM supports four types of query function returns

  1. Single/Mabye/Completable/observables/Flowable RxJava observed

  2. LiveData: Active observer in the JetPack library

  3. Flow: a Flow in a Kotlin coroutine

  4. Cursor: The primitive query result set of SQLite in Android. This return object cannot listen for database changes

I no longer recommend using RxJava in projects because concurrency is not easy and callback regions are easy to create. I recommend coroutines, or my open source project, Net, if I don’t think I can completely replace RxJava

All of the return types I’ve listed except Cursor support listening for changes in database query results in callbacks. The observer is triggered when the table you queried changes (even if the change does not match the result of the query) and then queries again. And all tables involved in your query will be notified when changes are made.

@Query("select * from Book")
fun find(a): Flow<Array<Book>>

@Query("select * from Book")
fun find(a): Observable<Array<Book>>

@Query("select * from Book")
fun find(a): LiveData<Array<Book>>

@Query("select * from Book")
fun find(a): LiveData<List<Book>> // List and Array are both acceptable

@Query("select * from Book")
fun find(a): Flow<Array<Book>>

@Query("select * from Book")
fun find(a): Cursor
Copy the code

Sample query Flow

val result = db.book().find()

GlobalScope.launch {
  result.collect { Log.d("Log"."result = $it")}}Copy the code
  1. We can use the function distinctUntilChanged to filter out duplicate data behavior (== indicates whether the data is the same or not, which is supported by the data class by default. For other member attributes, re-equals is required.)

    GlobalScope.launch {
      bookFlow.distinctUntilChanged().collect {
        Log.d("Log"."result = $it")}}Copy the code
  2. It is recommended to use with Net to automatically follow the life cycle and handle exceptions

features

I will be updating articles to introduce new features that follow the release

Pre-packaged databases

The database DB files can be placed in a path (File) or asset directory, and ROOM can rebuild by copying the pre-packaged database if the migration conditions are met.

Current application database version Pre-packaged database versions Update the application database version Migration strategy describe
2 3 4 Destructive migration Delete the application database reconstruction version 4
2 4 4 Destructive migration Copy the prepackaged database files
2 3 4 Manual migration Copy the pre-packaged database files and run manual migration 3->4
  1. The destructive migration mentioned here is invoked when ROOM is createdfallbackToDestructiveMigrationFunction, manual migration means no call
  2. It is recommended to useDataGripTo create the SQLite file, suffix.sqliteor.dbEssentially no difference, but Android generally uses DB
  3. Destructive migration must be enabled at the same time the pre-packaged database is enabled, otherwise it will be thrown

The pre-packaged database is only a rollback method, but still cannot retain user data completely. You need to migrate the database manually

A projection

When the queried table contains many fields and I only need two of them, I can create a POJO that contains only two fields instead of the previous Entity class.

ROOM queries do not necessarily return tables decorated with Entity names, as long as the corresponding field name can be projected to

Since expanded projections are introduced, it is emphasized that pojos used for expanded projections can also use certain annotations, such as ColumnInfo,PrimaryKey, Index

Data tables and POJO classes for simplification

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var bookId: Int = 0.var title: String = "drake"
)

// Suppose I just focus on the title and don't want to get extra ids
data class YellowBook(
    @ColumnInfo(name = "title")
    var virtual: String = "drake"
)
Copy the code

The original query and the query after using the expanded projection

// This is the original
@Query("select * from Book")
abstract fun findBook(a): List<Book>

// This is the expanded projection
@Query("select * from Book")
abstract fun findBook(a): List<YellowBook>
Copy the code

The target entity

The DAO annotations @INSERT, @update, and @Delete now have a new property entity

Similar to the expanded projection described above, use YellowBook for this tutorial

/ / the original
@Insert
fun insert(vararg book: Book): List<Long>

// Use the target entity
@Insert(entity = Book::class)
fun insert(vararg book: YellowBook): List<Long>
Copy the code
  1. The default values for the missing fields in YellowBook (ColumnInfo) are useddefaultValues) to insert,bookIdThe automatically generated primary key ID is used.
  2. The default values mentioned here are not the Kotlin parameter or field default values, but the default values in SQLite