Why was SQLite replaced by Room?
Table of contents
Why will SQLite be replaced?
Sqlite
It has a history of more than 20 years since its inception in 2000. In these more than 20 years, the Sqlite
original 1.0 version has been upgraded to the current 3.0 version. However, even with the latest 2.3.0 version supported by Android, it is still available on Android. There are still a series of shortcomings, as follows:
- There is no compile-time validation of raw SQL queries.
- A lot of boilerplate code is required to convert between SQL queries and data objects.
- While these APIs are powerful, they are quite low-level and require a lot of time and effort to use.
In response to Sqlite
a series of problems, the goals of realm
(applicable to ios, Android), ObjectBox
(cross-end, claiming to be the fastest), greenDao
and other local databases are to replace SQLite
, and even later, even the official released a new database - — Room
, is this to be completely eliminated Sqlite
?
The answer is no. The above databases greenDAO
are beingRoom
modified and optimized. It is a product developed by the development team of. They recommend the database to everyone on the Github homepage, but has not been updated for more than a year. Perhaps, the peak of Yes has passed, and it is difficult to stand on the top again, so they are slowly starting to use it. Losing hope for the future (personal guess).Sqlite
ObjectBox
greenDAO
greenDAO
ObjectBox
greenDAO
greenDao
So why SQLite
is it replaced Room
instead of greenDao
? Personally, I think this is entirely due to the official "official announcement". On the introduction page and usage guide found on the Android Developers official website SQLite
, you will find words like this:
we highly recommend using the Room Persistence Library as an abstraction layer for accessing information in your app's SQLite databases
.
The reasons for giving up SQLite
and persuading us to use Room
are SQLite
the disadvantages and Room
advantages.
Room
advantage:
- Simplified database migration path.
- Compile-time validation of SQL queries.
- Convenient comments minimize duplication and error-prone boilerplate code.
Integrated Room
Room contains three main components, namely:
- Database: A database class that holds a database and serves as the primary access point for the underlying connection to an application's persistent data.
- Entity: represents the data entity of the table in the application database.
- Dao: Data access object that provides methods that your application can use to query, update, insert, and delete data in the database.
Import dependencies
implementation("androidx.room:room-runtime:2.4.2")
annotationProcessor("androidx.room:room-compiler:2.4.2")
// 使用 Kotlin 注释处理工具 (kapt)
kapt("androidx.room:room-compiler:2.4.2")
Note: Kotlin plugin supports annotation processors such as Dagger or DBFlow. In order for them to work with Kotlin classes, plugins
kapt
need to be appliedkotlin-kapt
.
apply plugin: 'kotlin-kapt'
Click to view the latest version dependencies
There are three steps to use Room, namely Database, Entity, and Dao. Data can only be written, modified, and other operations after each creation is completed.
Entity (data entity)
In Room, you can define entities to represent the objects you want to store. Each entity corresponds to a table in the associated Room database, and each instance of an entity represents a row of data in the corresponding table.
The Kotlin version of Entity is written differently from the Java version of Entity. The normal way of writing the Java version of Entity is to create a new entity class and implement the get and set methods of the variables. The Kotlin version of Entity uses keywords. In Kotlin, the marked class is data class
used data class
. is a data class, which will automatically override the , , methods Any
of Kotlin's super class . For more detailed information, please go to → Kotlin Data Class (Data)equals(other: Any?)
hashCode()
toString()
The data class declared data class
as Room needs to be declared using @Entity
annotations. By default, Room will use the class name as the database table name. If you do not want the class name to be the same as the database table name, you can @Entity
set tableName
parameters in the annotation, such as: @Entity(tableName = "自定义的数据库表名")
, add parameters After that, the database table name generated by this class will no longer be the class name.
@Entity
data class Classes(
@PrimaryKey val classesId: String,
@ColumnInfo val classesName: String
)
Other notes
Annotation | Describe |
---|---|
@PrimaryKey | Used to uniquely identify each row in the corresponding database table |
@ColumnInfo | Define the column names of the data table |
@Ignore | Do not add fields to the data table |
@NonNull | The return value of a field or method cannot be null |
@Entity
For detailed usage, see: Using Room entities to define data
Dao (data access object)
Dao: Provides methods used by the rest of the application to interact with the data in the data table.
Dao is an interface class. There is no need to write a method body. Annotations can be used directly instead. Addition, deletion, modification and query correspond to the following four annotations:
Annotation | Describe |
---|---|
@Insert | increase |
@Delete | delete |
@Update | change |
@Query | check |
Annotations are used as follows:
@Dao
interface ClassesDao {
/**
* 增加多个班级
*/
@Insert
fun addMultipleClasses(vararg classes: Classes)
/**
* 删除某条数据
*/
@Delete
fun deleteClasses(classes: Classes)
/**
* 修改某条数据
*/
@Update
fun updateClasses(classes: Classes)
/**
* 查询classes表的数据
*/
@Query("select * from classes")
fun getAllClasses() : List<Classes>
}
In Room, the query with value is represented by colon. The following code indicates that the className to be searched is equal to the value passed when calling the interface.
@Query("select * from classes where className = :classesName")
fun getAllClasses(classesName: String) : List<Classes>
For detailed usage of Dao, see: Using Room DAO to access data
Database
Entity and Dao have been defined. The last step is to create a database class to save the data. The created class must meet the three conditions of the database:
- The class must be
RoomDatabase
an abstract class that extends the class. - For each Dao class associated with a database, the database class must define an abstract method that takes zero parameters and returns an instance of the Dao class.
- The class must be annotated with an annotation
@Database
containingentities
an array listing all data entities associated with the database (including the database version).
@Database(entities = [Classes::class/*数据库包含的实体*/], version = 1, exportSchema = false)
abstract class SchoolDatabase : RoomDatabase() {
/**
* 调用该方法获取Dao,来调用Classes表的接口方法
*/
abstract fun classDao(): ClassesDao
}
use
Room.databaseBuilder(applicationContext, SchoolDatabase::class.java, "数据库名称")
.build()
.classDao()
.addClasses(classes)
Effect
data migration
Why does the database need to be migrated?
You may encounter such a problem during the development of a local database. After modifying the fields of the database, a crash will occur upon restarting.
Some students who encounter this problem may use keywords like this to search: solutions to database field modification crashes. The search results will tell you to uninstall the app and reinstall it, and the problem will be solved. However, this seemingly correct method is actually fatal.
The local database is modified once, packaged and released. If the user uses the local database after updating the app, an Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
exception error will be reported.
If the interface of an app is not loaded within 8 seconds, 70% of users will be lost. This is the 8-second rule. So how many users will be lost if it crashes once? To solve the above problems, only the database can be migrated, and data migration is divided into automatic migration and manual migration.
Automatic migration
Automatic migration provides four annotations for automatic migration, which are:
- @DeleteTable (repeatable annotation used to declare a deleted table)
- @RenameTable (repeatable annotation used to declare a renamed table)
- @DeleteColumn (repeatable annotation used to declare deleted columns)
- @RenameColumn (repeatable annotation used to declare renamed columns)
How to use it:
// 版本更新前的数据库类.
@Database(
version = 1,
entities = [User::class]
)
abstract class AppDatabase : RoomDatabase() {
...
}
// 版本更新后的数据库类.
@Database(
version = 2,
entities = [User::class],
exportSchema = true,
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase() {
...
}
Since the official currently only provides the above four annotations for automatic migration, how to add fields has become a mystery. If you learned about the new data table fields for automatic migration while watching this article, I hope you can join us in the comment area. share.
Note:
1. Automatic migrationexportSchema
must be settrue
to avoid migration failure.
2. Automatic migration is only applicable to 2.4.0-alpha01 and higher versions. For lower versions and situations involving complex architecture changes, manual migration must be used.
Reference article: Room database migration
Manual migration
The first step in manual database migration is to modify the data table structure.
@Entity
data class Classes(
@PrimaryKey(autoGenerate = true) val classesId: Int = 0,
@ColumnInfo(name = "className") var classesName: String,
// 新增的列
@ColumnInfo(name = "classesNum") var classesNum: String
)
The method used for manual migration RoomDatabase.Builder
is addMigrations(@NonNull Migration... migrations)
to put the new Migration
class in to complete the migration.
The bottom layer of Room uses SQLite. Data migration uses the SQLite execSQL(String sql)
method to execute SQL statements to modify the database to complete the migration. The code is as follows:
var db: SchoolDatabase ?= null
// 单例模式的双重检查锁
fun getDataBase(context: Context) : SchoolDatabase{
if (db == null){
synchronized(SchoolDatabase::class.java){
if (db == null){
db = Room.databaseBuilder(context.applicationContext, SchoolDatabase::class.java, "school")
// .fallbackToDestructiveMigration()
// 添加数据迁移
.addMigrations(MIGRATION_1_TO_2)
.build()
}
}
}
return db as SchoolDatabase;
}
// Migration实例的两个参数指的是当前版本升级到什么版本
val MIGRATION_1_TO_2 : Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// 添加新的列使用 底层SQLite + sql 语法,其它增删改查亦是如此
database.execSQL("alter table classesNum add column 新增的列名 列名的数据类型 default 'null'")
}
}
Finally, don’t forget to change the @Database
annotation parameters on the database class name to the latest version, and run again to complete the migration.version
Reference: Android development basic tutorial
Used with RxJava
When Room performs database operations, it uses a sub-thread by default. If it needs to be executed in the main thread, it can be called to Room.databaseBuilder
disable allowMainThreadQueries()
Room's main thread query check. However, this operation is not recommended, as it will increase the probability of triggering ANR.
Thread
If you create a new one and implement the interface every time you perform a Room operation Runnable
, wouldn't it be easier to trigger ANR?
To this end, the official has launched a library that is used in conjunction with Rxjava to facilitate switching between threads.
// optional - RxJava3 support for Room
implementation("androidx.room:room-rxjava3:2.4.2")
// RxJava3
implementation ("io.reactivex.rxjava3:rxjava:3.1.3")
implementation ("io.reactivex.rxjava3:rxandroid:3.0.0")
For specific operations combined with RxJava, please refer to the articles:
1. Room database practice: use and encapsulation with RxJava
2. Room with RxJava2, usage methods, experience and precautions
Code for this article: [Android] Room - SQLite replacement Demo
Reference document
1. Android Room code example
2. Android Developers —— Room
3. Android Developers —— Room usage guide
4. Android Developers —— Defining data using Room entities