Jetpack is introduced

Jetpack is an official set of Android development components that are designed to improve our development efficiency, eliminate template code, and build high quality powerful applications. Here are the plans to share:

Room is introduced

Room is an ORM library in the Jetpack component that abstracts a layer of Android’s native Sqlite Api to make it easier for developers to work with the database. ORM libraries like GreenDao, Realm, etc. Not only is Room a recommended component for Android in Jetpack, but more importantly, it has some advantages in terms of performance.

This article mainly introduces three aspects about Room:

  1. Room of the configuration
  2. The use of the Room
  3. Room source interpretation

The use of the Room

Room configuration


 def room_version = 1.1.1 ""

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"


Copy the code

Room use

When using data, three parts of Room should be mainly involved:

  1. DataBase: Creates a DataBase instance
  2. Entity: indicates the Entity corresponding to the table in the database
  3. Dao: A method of operating a database

Here are some of them

Creating a Database Instance


@Database(entities = {Video.class, User.class}, version = 1)
//@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase INSTANCE;
    private static final Object sLock = new Object();

    public abstract UserDao userDao();

    public abstract VideoDao videoDao();


    public static AppDatabase getInstance(Context context) {
        synchronized (sLock) {
            if (INSTANCE == null) {
                INSTANCE =
                        Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db").allowMainThreadQueries() //room Default database queries cannot be executed on the main thread unless set.build(); }returnINSTANCE; }}}Copy the code

Customize an abstract class that inherits from RoomDatabase. Here are a few things to note:

  1. This class is abstract because Room will give you the real implementation.
  2. Database instance generation is time-consuming, so singletons are recommended here
  3. The entity in the @DATABASE annotation is the table to be created.
  4. You need to provide a way to get the DAO.

Entity

An entity is an entity corresponding to a table. You need to add the @Entity annotation on this class


@Entity()
public class User {

    @PrimaryKey
    public int uid;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "video_id")
    public int videoId;

    @Ignore
    public List<Video>  videos;

}

Copy the code

Here are some of the annotations used by an Entity:

  1. @Entity

Indicates a mapping class for a table. You can customize the table name

@Entity(tableName = "Video")


Copy the code
  1. @PrimaryKey

Define a primary key

    @PrimaryKey
    public int uid;

Copy the code
  1. @ColumnInfo

User-defined field names in the database

@ColumnInfo(name = "name")
    public String name;

Copy the code
  1. @ForeignKey

Define foreign key constraints

  1. @Ignore

If a field in our class does not want to be created in the table, we can use this annotation to ignore it.



@Entity(tableName = "Video", foreignKeys = @ForeignKey(entity = User.class,
        parentColumns = "uid", childColumns = "uid")) public class Video {@primaryKey public int vid; @ColumnInfo(name ="name")
    public String name;

    @ColumnInfo(name = "last_name")
    public String length;

    public int uid;

    @Ignore
    public String ignore;

}

Copy the code

Dao

The Dao provides methods to operate on the database.



@Dao
public interface UserDao {

    @Query("SELECT * FROM User")
    List<User> getAll();

    @Query("SELECT * FROM User WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM User WHERE name LIKE :name ") User findByName(String name); @Insert void insertAll(User... users); @Delete void delete(User user); @query (@transaction, @query)"SELECT uid, name from User")
    List<UserAllVideos> loadUsers();




}


Copy the code

Define an interface file and add the @DAO annotation, which of course is implemented by Room. Each method according to the business needs to realize the increase, delete, change and check, here is also relatively simple.

Scene: the class


 		User user = new User();
        user.uid = 1;
        user.name = "love";
    
        AppDatabase.getInstance(this).userDao().insertAll(user);



Copy the code

Insert a piece of User data into the database


 Video video = new Video();
        video.vid = 1;
        video.name = "love";
        video.length = "123";
        video.ignore = "ignore";
        video.uid = 1;
        AppDatabase.getInstance(this).videoDao().insertAll(video);


Copy the code

Insert a Video.

Now that we’ve inserted the data, let’s get the data. Normal single table data should be fine, I will not demonstrate. There is only one case, associated query, where we associate User and Video using a foreign key. Then how can we query the associated Video when querying the User? Here’s how Room works:

Create a UserAllVideos class, noting that it is not an Entity class and has no @Entity annotation


public class UserAllVideos {

    @Embedded
    public User user;
    @Relation(parentColumn = "uid", entityColumn = "uid")
    public List<Video> videos;
}


Copy the code

Here are two notes to explain:

  1. @Embedded

Its role is to bring in all the fields in User

  1. @Relation

His role is similar to ForeignKey, which is associated with User and Video for our joint query

Then add a query method loadUsers in UserDao, so that the User information and its associated Video information can be queried out.


List<UserAllVideos> userAllVideos = AppDatabase.getInstance(this).userDao().loadUsers();
        Log.d(TAG, userAllVideos.get(0).videos.get(0).name);


Copy the code

TypeConverter

@typeconverter converts objects. For example, Room does not allow references to objects, but we can use TypeConverter to convert objects. For example, if we want to add a List field to a User Entity, we can create a Converters class. Define two conversion methods that convert String and List before each other. Then you can configure the Converters in the Database as follows:

Public class Converters {@typeconverter public static List<Video> revert(String STR) {// Use the Gson method to convert a String to a List try {return new Gson().fromJson(str, new TypeToken<List<Video>>(){}.getType());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            returnnull; }} @typeconverter public static String converter(List<Video> videos) {}} @typeconverter public static String converter(List<Video> videos)returnnew Gson().toJson(videos); }}Copy the code

Configure TypeConverters in the Database


@Database(entities = {Video.class, User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {


Copy the code

Database Upgrade

To create a Migration, you need a starting version and an upgraded version. In the migrage method are the details of the Database upgrade, and then configure Migration in the Database. Multiple Migration can also be configured if multiple version upgrades are involved

static final Migration MIGRATION_1_2 = new Migration(1, 2) {// Upgrade to version 2 @override public void migrate(SupportSQLiteDatabase database) {database.execSQL("CREATE TABLE Topic (id INTEGER , name TEXT )"); }}; Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class,"video.db").allowMainThreadQueries() //room Default database queries cannot be executed on the main thread unless set.addMigrations(MIGRATION_1_2).build();Copy the code

Integrated RxJava2

Room supports RxJava2, and the returned results can be encapsulated in Flowable.

The above is the basic use of Room. This document is just a beginner. Please refer to other documents for more detailed use. Here is a brief introduction to the Room source code.

Room source code introduction

Gradle relies on Room and can only see part of the source code. To see the full source code of Room, click here.

Creating a Database Instance

Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "video.db").allowMainThreadQueries() //room Default database queries cannot be executed on the main thread unless set.addMigrations(MIGRATION_1_2).build();Copy the code

At first glance, Room takes the builder mode and adds a lot of configuration. Let’s look directly at the build method


        @NonNull
        public T build() {
            //noinspection ConstantConditions
            if (mContext == null) {
                throw new IllegalArgumentException("Cannot provide null context for the database.");
            }
            //noinspection ConstantConditions
            if (mDatabaseClass == null) {
                throw new IllegalArgumentException("Must provide an abstract class that"
                        + " extends RoomDatabase");
            }

            if(mMigrationStartAndEndVersions ! = null && mMigrationsNotRequiredFrom ! = null) {for (Integer version : mMigrationStartAndEndVersions) {
                    if (mMigrationsNotRequiredFrom.contains(version)) {
                        throw new IllegalArgumentException(
                                "Inconsistency detected. A Migration was supplied to "
                                        + "addMigration(Migration... migrations) that has a start "
                                        + "or end version equal to a start version supplied to "
                                        + "fallbackToDestructiveMigrationFrom(int... "
                                        + "startVersions). Start version: "+ version); }}}if (mFactory == null) {
                mFactory = new FrameworkSQLiteOpenHelperFactory();
            }
            DatabaseConfiguration configuration =
                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
                            mCallbacks, mAllowMainThreadQueries,
                            mJournalMode.resolve(mContext),
                            mRequireMigration, mMigrationsNotRequiredFrom);
            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
            db.init(configuration);
            return db;
        }

Copy the code

The main purpose of this method is to create the database instance and then initialize the configuration. In the previous usage section, we noted that AppDatabase is an abstract class, so its actual implementation was actually generated at compile time, located at


build/generared/source/apt/debug/com.xray.sample.room/AppDatabase_Impl

Copy the code

His instantiated by Room. GetGeneratedImplementation method, actually is the reflection.


 static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
        final String fullPackage = klass.getPackage().getName();
        String name = klass.getCanonicalName();
        final String postPackageName = fullPackage.isEmpty()
                ? name
                : (name.substring(fullPackage.length() + 1));
        final String implName = postPackageName.replace('. '.'_') + suffix;
        //noinspection TryWithIdenticalCatches
        try {

            @SuppressWarnings("unchecked")
            final Class<T> aClass = (Class<T>) Class.forName(
                    fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
            return aClass.newInstance();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("cannot find implementation for "
                    + klass.getCanonicalName() + "." + implName + " does not exist");
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot access the constructor"
                    + klass.getCanonicalName());
        } catch (InstantiationException e) {
            throw new RuntimeException("Failed to create an instance of "+ klass.getCanonicalName()); }}Copy the code

The actual database is created in the init method


    @CallSuper
    public void init(@NonNull DatabaseConfiguration configuration) {
        mOpenHelper = createOpenHelper(configuration);
        boolean wal = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
            mOpenHelper.setWriteAheadLoggingEnabled(wal);
        }
        mCallbacks = configuration.callbacks;
        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
        mWriteAheadLoggingEnabled = wal;
    }



Copy the code

CreateOpenHelper this method, his implementation in AppDatabase_Impl, many code, mainly is the database creation, table creation, upgrade processing and so on.

Dao method calls

Again in the AppDatabase_Impl generation class, we find two places where the Dao is instantiated. Here we find that the Dao implementation classes were also generated at compile time, UserDao_Impl and VideoDao_Impl


 @Override
  public UserDao userDao() {
    if(_userDao ! = null) {return _userDao;
    } else {
      synchronized(this) {
        if(_userDao == null) {
          _userDao = new UserDao_Impl(this);
        }
        return _userDao;
      }
    }
  }

  @Override
  public VideoDao videoDao() {
    if(_videoDao ! = null) {return _videoDao;
    } else {
      synchronized(this) {
        if(_videoDao == null) {
          _videoDao = new VideoDao_Impl(this);
        }
        return_videoDao; }}}Copy the code

Dao on the implementation class everyone to see, are compiled generated template code.

Template code generation

Room template code generation is achieved through the annotation processor, the specific source code here, although the idea is simple, but to clear the inside of the details is more troublesome.

thinking

  1. Why does Room not allow object object reference links in entities

Reference documentation

  1. Room source
  2. Outside the room key
  3. Room stores complex types
  4. room