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:
- Room of the configuration
- The use of the Room
- 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:
- DataBase: Creates a DataBase instance
- Entity: indicates the Entity corresponding to the table in the database
- 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:
- This class is abstract because Room will give you the real implementation.
- Database instance generation is time-consuming, so singletons are recommended here
- The entity in the @DATABASE annotation is the table to be created.
- 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:
- @Entity
Indicates a mapping class for a table. You can customize the table name
@Entity(tableName = "Video")
Copy the code
- @PrimaryKey
Define a primary key
@PrimaryKey
public int uid;
Copy the code
- @ColumnInfo
User-defined field names in the database
@ColumnInfo(name = "name")
public String name;
Copy the code
- @ForeignKey
Define foreign key constraints
- @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:
- @Embedded
Its role is to bring in all the fields in User
- @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
- Why does Room not allow object object reference links in entities
Reference documentation
- Room source
- Outside the room key
- Room stores complex types
- room