Save data to the persistent Room library

Room is an ORM library provided by Google. Room provides three main components

  • @DATABASE: @Database is used to annotate classes, and the annotated class must be an abstract class inherited from RoomDatabase. This class is used to create databases and daOs
  • @entity: @entity is used to annotate the Entity class
  • Dao: Provides a way to access the database

Use the Room

  • Add the repositories
allprojects {
    repositories {
        google()
    }
}
Copy the code
  • Add the dependent
//Room database implementation"Android. Arch. Persistence. Room: the runtime: 1.1.0 - beta1"
annotationProcessor "Android. Arch. Persistence. Room: the compiler: 1.1.0 - beta1"
Copy the code

Now that the Room dependency has been added, we need to rebuild to use Room.

Define the Entity

  • Create Java classes and use Entity annotations

Defining an Entity simply requires adding the @entity annotation to the class

@Entity
public class User{
}
Copy the code
  • Specify an alias for the table
@Entity(tableName = "TableName_User")
public class User{
}
Copy the code
  • Create a primary key

We also need to set the primary key for it. Each Entity must define at least 1 field as the primary key, even if there is only one member variable

@Entity
public class User{
    @PrimaryKey
    public long userId;
}
Copy the code
  • Create an autoincrement primary key

Each entity defines at least one field as the primary key. You can set the autoGenerate property of @primaryKey to true to set the automatic ID

@Entity
public class User{
    @PrimaryKey(autoGenerate = true)
    public int id;
}

Copy the code
  • Create a compound primary key

If the Entity has a compound primary key, you can specify the primary key using the @Entity primaryKeys property.

@Entity(primaryKeys = {"firstName"."lastName"})
public class User{
    public String firstName;
    public String lastName;
}
Copy the code
  • Ignore member attributes.

By default, Room creates a field in the database for each member attribute, annotating it with Ignore if it is not needed

@Ignore
public String otherInfo;
Copy the code
  • Specify a member property alias

The @columnInfo annotation is needed if we need to rename the field names in which member data is stored in the database

@ColumnInfo(name = "user_name")
public String name;
Copy the code
  • Adding indexes

Adding indexes speeds up the query of data. Indexes can be added in Room through the indices property of @Entity.

@Entity(indices = {@Index("firstName"),@Index(value = {"lastName"."address"})})
class User {
    @PrimaryKey
    public int id;
    public String firstName;
    public String address;
    public String lastName;
}
Copy the code
  • Ensure uniqueness

We can ensure the uniqueness of a field, or of a group of fields, by setting @index’s unique property to true.

@Entity(indices = {@Index(value = {"firstName"."lastName"},unique = true)})
class User {
    @PrimaryKey 
    public int id;
    public String firstName;
    public String lastName;
}
Copy the code
  • Nested objects

Room allows us to embed another entity within an entity with the @Embedded annotation, creating a table that uses the current entity and all the fields that created the entity.

@Entity
class User {
    @PrimaryKey
    public int id;
    public String firstName;
    @Embedded
    public Address address;
}
class Address {
    public String state;
    public String city;
    public String street;
    public int postCode;
}
Copy the code
  • Nested multiple objects

When multiple objects are nested in a class, if they have the same field, we need to provide the prefix attribute when we call @Embedded to add a prefix to all fields stored in the database for the object. The generated column name is prefix + column name

public class User {
  @PrimaryKey(autoGenerate = true) 
  public int id;
  @Embedded(prefix = "first")
  Address1 address;
  @Embedded(prefix = "second")
  Address2 address2;
}
Copy the code

DAO (Data Access Object) is used

  • Create Dao

The Dao is an interface, so we just need to annotate @DAO on our interface

@Dao
public interface UserDao {
    
}
Copy the code
  • Insert data

The @insert annotation helps us to Insert data. The following three methods indicate that we can pass in three different types. OnConflict is used by us to specify the strategy when a conflict occurs. For example, if the unique attribute of @index is set to true, problems will occur when we insert new data and duplicate old data. Here, we can customize the strategy when this problem occurs. For details, see OnConflictStrategy

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);
    @Insert
    public void insertBothUsers(User user1, User user2);
    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}
Copy the code
  • Insert data to return rowId

In the example above, we return void when we insert data. Instead, we can return a rowId of type long, which identifies the inserted ID

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public Long insertUsers(User... users);
    @Insert
    public Long[] insertBothUsers(User user1, User user2);
    @Insert
    public List<Long> insertUsersAndFriends(User user, List<User> friends);
}
Copy the code
  • Update the data

We can use the @update annotation method to Update the columns in the database that are the same as the primary key of the parameter entity. If there is no primary key in the database that is the same as the parameter entity, nothing is done to the database.

@Dao
public interface UserDao {
    @Update
    public int updateUsers(User... users);
}
Copy the code

The @update conflict resolution strategy is the same as Insert in that it can return an int indicating the number of rows affected

  • Delete the data

Deleting data is similar to inserting data. It also supports three methods, which can also return void or int. Use delete to delete the same database fields in the primary key and parameter entities

@Dao
public interface UserDao {
    @Delete(onConflict = OnConflictStrategy.REPLACE)
    public void deleteUsers(User... users);
    @Delete
    public void deleteBothUsers(User user1, User user2);
    @Delete
    public void deleteUsersAndFriends(User user, List<User> friends);
}
Copy the code
  • The query

The value of @query is an SQL statement that can be executed by SQlit. @query supports Query deletion and update, but does not support insert

Room checks at compile time, and if the code contains syntax errors, or if the table does not exist, Room will display an error message at compile time.

Eg: Query all fields in the user table

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}
Copy the code
  • Query by condition

@query Can Query the corresponding result based on the condition

Eg: Query all users whose age = ageNum from the user table.

@dao public interface UserDao {// Pass a single argument @query ("SELECT * FROM user WHERE age = :ageNum")
    public User[] loadAllUsersOlderThan(int ageNum);
}
Copy the code

Query by multiple criteria, or reference the same parameter more than once

@dao public interface UserDao {// Query all users whose minAge is greater than that of maxAge @query ("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") public User[] loadAllUsersBetweenAges(int minAge, int maxAge); FirstName = search or lastName = search @query ("SELECT * FROM user WHERE firstName LIKE :search " + "OR lastName LIKE :search")
    public List<User> findUserWithName(String search);
}
Copy the code

Room also allows you to pass in a collection of arguments

@dao public interface MyDao {// Query all people living in regions list @query ("SELECT firstName, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
Copy the code
  • @query The Query returns partial column values

Usually we don’t want to return all the fields in the database, we only care about the columns we need to save resources. Since Room allows you to return any Java object, at this point you can define your own return object.

public class NamePart {
    public String firstName;
    public String lastName;
}
Copy the code

Use this object in the query method

@Dao
public interface User {
    @Query("SELECT firstName, lastName FROM user")
    public List<NamePart> loadFullName();
}
Copy the code

This gives us a list containing fields in the name part of the database, as shown above.

  • RXjava2 support

Room can also return the results to RXJava Publisher and Flowable for easy follow-up. Using this feature requires the following configuration in Gradle.

//RXjava2 supports adding this option to return Flowable in RxJava or Publisher to official RxJava supportforRoom (use 1.1.0 - walkfor latest alpha)
implementation "Android. Arch. Persistence. Room: rxjava2:1.1.0 - beta1"
compile 'the IO. Reactivex. Rxjava2: rxandroid: 2.0.1'
Copy the code

Return other Rxjava results

  • Returns the Cursor

When we don’t want Room to do anything internally with the result returned, we can have our program return a Cursor object

@Dao
public interface User {
    @Query("SELECT * FROM user WHERE userName = :userName")
    public Cursor loadRawUsersByUserName(String userName);
}
Copy the code
  • Associated query of multiple tables

Also because we can execute any SQL statement in the @Query() annotation, room can support almost any Query, and we can perform a variety of Query operations based on the relationships established between tables. Eg: Left connection, right connection, internal connection, full connection. SQL > query multiple tables.

Database definition

The definition of the Room database is simple enough to define an abstract subclass of RoomDatabase annotated by @Database. Where entities is all entities used in the database, version is the version number of the current database. We also need to return all database access object DAOs from this database in the form of abstract methods below. For program use.

@Database(entities = {User.java}, version = 1) @TypeConverters({Converter.class}) public abstract class AppDatabase extends RoomDatabase { public abstract  UserDao userDao(); }Copy the code
  • Room TypeConverter @typeconverter

Room supports the storage of strings and primitive data types, as well as other Entity annotation types for properties of the main class. We need a type converter if we encounter a store that is not of these types.

Public class Converters {@typeconverter public static Date fromTimestamp(Long value) {return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        returndate == null ? null : date.getTime(); }}Copy the code

We define two methods in the converter, one is to convert data to a timestamp for storage, and the other is to take the timestamp out and convert it to data for our use. So how do we apply it to room?

  • Apply the data type converter

TypeConverter is used in the Room converter by annotating @Typeconverters ({}) to an abstract subclass of RoomDatabase. So we can use our custom Converter inside the Room.

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

ROOM Database Upgrade

When our App is released, we may sometimes add or delete the structure of the original table, at this time, we need to upgrade our database. Every change to the database structure requires an upgrade to the database version. Room provides the Migration class to help with database upgrades. We can provide the start and end versions of the database in the Migration constructor.

Room.databaseBuilder(getApplicationContext(), Database.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 `Address` (`id` INTEGER, "
                + "`address` 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"); }};Copy the code

Where AppDatabase is the type of the RoomDatabase abstract subclass and database-name is the database name. AddMigration is the Migration object that adds different versions of the upgrade. During the upgrade, the program will detect the current version and execute the Migrate method of Migration class level by level to complete the structural transformation of the database.