The Library version of this article is as follows:

  • Androidx. Room: room – the runtime: 2.1.0 – alpha03
  • Androidx. room: Room-compiler :2.1.0-alpha03

Review the related SQLiteOpenHelper classes for Android

Let’s start with a class diagram of an Android database:

SQLiteOpenHelper is an abstract class that usually implements its own database. It needs to inherit SQLiteOpenHelper, build tables in OnCreate(), handle version migration in onUpgrade(), etc. SQLiteOpenHelper is a helper class, where SQLiteDatabase really represents a database. SQLiteDatabase provides methods to open the database, add, delete, change, and query the database. We usually execute SQL statements to operate on the database, but the official package is more convenient to use.

SQLiteProgram is the abstract class used to compile SQLite programs. Member variables contain SQL statements, table field name data, and corresponding field values. SQLiteStatement inherits SQLiteProgram and provides the following methods to execute the statement. SQLiteQurey also inherits SQLiteProgram and represents the execution of the query. Android’s database operations separate queries from other operations. SQLiteCursor cursor is generated by executing SQLiteQurey through SQLiteDirectCursorDriver to retrieve data. Create tables, delete tables, create indexes, and so on by using SQLiteStatement. Excute (). Update, and delete by SQLiteStatement. ExecuteUpdateDelete (); Insert the data through SQLiteStatement. ExecuteInsert (). Room is encapsulated on the basis of the original.

Class diagram structure of Room

Above, there are some Support at the beginning of the interface, the interface is androidx. Sqlite: sqlite: 2.0.0 garage, this is done to the android original database operation interface. SupportSQLiteDatabase corresponds to SQLiteDatabase, SupportSQLiteOpenHelper corresponds to SQLiteOpenHelper, SupportSQLiteProgram corresponds to SQLiteProgram, and so on.

Some classes at the beginning of the Framework are implementations of some Support interfaces; Sqlite: SQLite-Framework :2.0.0. The FrameworkSQLiteDatabase implementation has a member variable SQLiteDatabase. The interfaces implemented are handed over to SQLiteDatabase. FrameworkSQLiteOpenHelper, FrameworkSQLiteProgram, FrameworkSQLiteStatement are the routines, use the decorator pattern, the realization of the real and the android original database operations. FrameworkSQLiteOpenHelperFactory factory returns is FrameworkSQLiteOpenHelper OpenHelper class, FrameworkSQLiteOpenHelper. OpenHelper SQLiteOpenHelper inheritance.

These classes exist in the androidx. Room: Room-Runtime :2.10 library, which is based on interfaces like Support (e.g. SupportSQLiteDatabase) and the Framework implementation (implementation of Framework SQliteDatabase) are encapsulated again. Room.databasebuilder () is used to generate an implementation of AppDatabase.

Room database table creation

AppDatabase is an abstract class. The real implementation is AppDatabase_Impl, which is generated with compile-time annotations from androidX. room: room-Compiler :$room_Version. AppDatabase implementation class is made up of RoomDatabase. Builder. The build () to create, first look at the build method implementation:

public T build(a) {...if (mFactory == null) { / / SQLiteOpenHelper factory
            mFactory = new FrameworkSQLiteOpenHelperFactory();
        }
    //DatabaseConfiguration is a data configuration class
    // Store context, database name, SQLiteOpenHelperFactory, etc
    DatabaseConfiguration configuration =
        new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
                                  mCallbacks, mAllowMainThreadQueries, mJournalMode.resolve(mContext),
                                  mQueryExecutor,
                                  mMultiInstanceInvalidation,
                                  mRequireMigration,
                                  mAllowDestructiveMigrationOnDowngrade, mMigrationsNotRequiredFrom);
    //DB_IMPL_SUFFIX = "_Impl", db implementation is AppDatabase_Impl
    T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
    // Init is implemented in RoomDatabase
    db.init(configuration);
    return db;
}

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;
	// Klass package name + "_Impl", reflection call newInstance() to generate instance
    final Class<T> aClass = (Class<T>) Class.forName(
        fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
    return aClass.newInstance();
}

/ / RoomDatabase. The init method
public void init(@NonNull DatabaseConfiguration configuration) {
    / / table
    mOpenHelper = createOpenHelper(configuration);
    boolean wal = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
        // Whether to enable logs
        mOpenHelper.setWriteAheadLoggingEnabled(wal);
    }
    mCallbacks = configuration.callbacks;
    // Query Executor: Determines the thread in which the query will be executed
    mQueryExecutor = configuration.queryExecutor; 
    mAllowMainThreadQueries = configuration.allowMainThreadQueries;
    mWriteAheadLoggingEnabled = wal;
    if(configuration.multiInstanceInvalidation) { mInvalidationTracker.startMultiInstanceInvalidation(configuration.context, configuration.name); }}Copy the code

RoomDatabase createOpenHelper method is abstract, implementation in AppDatabase_Impl, positioning to AppDatabase_Impl. CreateOpenHelper method

  @Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
      //_openCallback is a locally anonymous internal instance of RoomOpenHelper.
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
          // Execute the table builder
        _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`first_name` TEXT, `name` TEXT, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))");
      }

      @Override
      protected void onCreate(SupportSQLiteDatabase _db) {
        if(mCallbacks ! =null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { mCallbacks.get(_i).onCreate(_db); }}}@Override
      public void onOpen(SupportSQLiteDatabase _db) {
        mDatabase = _db;
        internalInitInvalidationTracker(_db);
        if(mCallbacks ! =null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { mCallbacks.get(_i).onOpen(_db); }}}},"e216f2ddb0b894667088e1e7fec58cdd"."07bca20d2ba295fc9d4acbe7a3f64d4b");
      
      / / SupportSQLiteOpenHelper. The Configuration database related Configuration
      / / store the context, the name name (database), SupportSQLiteOpenHelper. The Callback object
    final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
        .name(configuration.name)
        .callback(_openCallback)
        .build();
      / / plant generates SupportSQLiteOpenHelper, here the factory default is FrameworkSQLiteOpenHelperFactory
      // Default values are assigned in roomDatabase.build ()
    final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
    return _helper;
  }

/ / FrameworkSQLiteOpenHelperFactory class implementation
public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
    @Override
    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
        / / new a FrameworkSQLiteOpenHelper, then see FrameworkSQLiteOpenHelper constructor
        / / get SupportSQLiteOpenHelper. The Configuration of the context, the name, the callback
        return newFrameworkSQLiteOpenHelper( configuration.context, configuration.name, configuration.callback); }}/ / FrameworkSQLiteOpenHelper constructor
FrameworkSQLiteOpenHelper(Context context, String name,
                          Callback callback) {
    mDelegate = createDelegate(context, name, callback);
}
/ / FrameworkSQLiteOpenHelper createDelegate method
private OpenHelper createDelegate(Context context, String name, Callback callback) {
    final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
    / / OpenHelper FrameworkSQLiteOpenHelper inner classes
    return new OpenHelper(context, name, dbRef, callback);
}

//OpenHelper inherits android SQLiteOpenHelper
static class OpenHelper extends SQLiteOpenHelper {
    final FrameworkSQLiteDatabase[] mDbRef;
    final Callback mCallback;
    // see b/78359448
    private boolean mMigrated;

    OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
               final Callback callback) {
        super(context, name, null, callback.version,
              new DatabaseErrorHandler() {
                  @Override
                  public void onCorruption(SQLiteDatabase dbObj) {
                      FrameworkSQLiteDatabase db = dbRef[0];
                      if(db ! =null) { callback.onCorruption(db); }}}); mCallback = callback; mDbRef = dbRef; }@Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // Database initialization, mCallback is RoomOpenHelpermCallback.onCreate(getWrappedDb(sqLiteDatabase)); }}/ / RoomOpenHelper. OnCreate method
@Override
public void onCreate(SupportSQLiteDatabase db) {
    // Update Identity (skipped here, not in depth)
    updateIdentity(db);
    //mDelegate is the RoomOpenHelper's internal class Delegate, Delegate is an abstract class,
    CreateAllTables, onCreate, onOpen, etc
    //mDelegate is implemented here as an instance of _openCallback in the AppDatabase_Impl. CreateOpenHelper method
    
    // Call the table building method
    mDelegate.createAllTables(db);
    // Call onCreate
    mDelegate.onCreate(db);
    
    // Look at the implementation of _openCallback. CreateAllTables implements the table construction clause
}

Copy the code

Room database insertion procedure

Room data access only needs to define a DAO interface where methods and annotations are defined.

@Dao public interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insert(User user); } // Call appDatabase.getInstance ().userdao ().insert(user);Copy the code

The AppDatabase implementation class AppDatabase_Impl was mentioned earlier, while the UserDao implementation class is UserDao_Impl. UserDao_Impl is also an automatically generated implementation class at compile time, so take a look at AppDatabase_impl.userDAO ()

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

Implementation of the UserDao_Impl interface

/ / UserDao_Impl constructor
public UserDao_Impl(RoomDatabase __db) {
    this.__db = __db;
    // Create an EntityInsertionAdapter object
    this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
        
      // Insert data into the SQL statement
      @Override
      public String createQuery(a) {
        return "INSERT OR ABORT INTO `User`(`first_name`,`name`,`id`) VALUES (? ,? ,?) ";
      }

      // Bind the inserted field to the SQL statement '? 'a placeholder
      @Override
      public void bind(SupportSQLiteStatement stmt, User value) {
        if (value.firstName == null) {
          stmt.bindNull(1); // Corresponding to the first "?" Placeholder to update the value of first_name
        } else {
          stmt.bindString(1, value.firstName);
        }
        if (value.name == null) {
          stmt.bindNull(2);
        } else {
          stmt.bindString(2, value.name);
        }
        stmt.bindLong(3, value.id); }}; }@Override
  public void insert(final User user) {
    __db.beginTransaction();
    try { // Start transaction to insert data
      __insertionAdapterOfUser.insert(user);
      __db.setTransactionSuccessful();
    } finally{ __db.endTransaction(); }}Copy the code

Can know from the above code, finally open a transaction, call EntityInsertionAdapter. Insert method for data insert, EntityInsertionAdapter is an abstract class, using generic encapsulates the support for all types of data insert, The implementation class must implement the createQuery and bind methods. The createQuery method returns an SQL statement, while the bind method, which binds the data class, corresponds to the SQL statement.

EntityInsertionAdapter. Insert method is as follows:

public final void insert(T entity) {
        final SupportSQLiteStatement stmt = acquire(); // Create a SupportSQLiteStatement, which calls the createQuery method
        try {
            bind(stmt, entity);// Bind (SupportSQLiteStatement STMT, T value)
            stmt.executeInsert();// Execute the SQL statement
        } finally{ release(stmt); }}Copy the code

The final data class is inserted to execute SQL statements through the SupportSQLiteStatement interface. Looking back at the class structure diagram at the beginning of this article, SupportSQLiteStatement is an interface and implementation are FrameworkSQLiteStatement, and internal implementation is a delegate FrameworkSQLiteStatement SQLiteStatement to perform, Finally, it calls the original Android SQLiteStatement class.

Room database query process

The same goes for queries. By defining a Dao interface and @query annotation, the code looks like this:

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    User findById(String id);
}    
Copy the code

UserDao_Impl. FindById is implemented as follows:

  @Override
  public User findById(final String id) {
    final String _sql = "SELECT * FROM user WHERE id = ?";
    // Create an SQLite query executor using SQL
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
    int _argIndex = 1;
    if (id == null) {
      _statement.bindNull(_argIndex);
    } else {
      _statement.bindString(_argIndex, id);// Bind parameter ID
    }
    final Cursor _cursor = DBUtil.query(__db, _statement, false);// Execute the query statement
      // Get data at cursor and assign it to User
    try {
      final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
      final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
      final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
      final User _result;
      if(_cursor.moveToFirst()) {
        _result = new User();
        _result.firstName = _cursor.getString(_cursorIndexOfFirstName);
        _result.name = _cursor.getString(_cursorIndexOfName);
        _result.id = _cursor.getInt(_cursorIndexOfId);
      } else {
        _result = null;
      }
      return _result;
    } finally{ _cursor.close(); _statement.release(); }}Copy the code

conclusion

  • Room encapsulates the original data classes of Android, The SQLiteOpenHelper SQLiteDatabase, SQLiteProgram SQLiteStatement, SupportSQLiteOpenHelper SQLiteQurey abstract out the corresponding interface, SupportSQLiteDatabase, SupportSQLiteProgram, SupportSQLiteStatement, SupportSQLiteQurey. The next set of classes that provide a Framework letter beginning are implementations of the Support interface.
  • Abstracting out a set of Support interfaces gives developers the ability to implement a set of interfaces that are not based on sqLite databases. An example is realm, the open source library that can replace SQLite
  • Room is a DATABASE framework based on THE DAO architecture, using apt compile time automatically generated code to implement a large number of module code