When you’re doing data persistence on Android, you often use databases. In addition to SQLiteHelper, there are several mature three-party libraries available in the industry.
This paper makes a horizontal comparison of these tripartite libraries for everyone to make a reference in the selection of technology.
- Room
- Relam
- GreenDAO
- ObjectBox
- SQLDelight
For an Article type data store, we design the database table as follows: | Field Name | Type | Length | Primary | Description | | : — | — – | : — | — – | : – | – – | | | id Long | | yes id | | article 20 | author | Text 10 | | | The author | | in the | Text 20 | | | title | | desc | Text 50 | | | the | | | url Text 50 | | | article links | | likes | Int 10 | | | thumb up several | | updateDate | Text 20 | | | | update date
1. Room
Room is the official ORM framework for Android. It provides an ABSTRACTION layer based on SQLite, which hides SQLite access details and makes it easier to implement a Single Source of Truth with the official recommended AAC component.
Developer.android.com/training/da…
Engineering depends on
implementation "androidx.room:room-runtime:$latest_version"
implementation "androidx.room:room-ktx:$latest_version"
kapt "androidx.room:room-compiler:$latest_version" // Annotation handler
Copy the code
Entity defines the database table structure
Room uses data class to define the table structure of the Entity representing the db, @primarykey to identify the PrimaryKey, and @columninfo to define the field names of attributes in the db
@Entity
data class Article(
@PrimaryKey
val id: Long.val author: String,
val title: String,
val desc: String,
val url: String,
val likes: Int.@ColumnInfo(name = "updateDate")
@TypeConverters(DateTypeConverter::class)
val date: Date,
)
Copy the code
Room is based on SQLite, so it can only store primitive data. Any object type must be converted to primitive via TypeConvert:
class Converters {
@TypeConverter
fun fromString(value: String?).: Date? {
return format.parse(value)
}
@TypeConverter
fun dateToString(date: Date?).: String? {
return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(date)
}
}
Copy the code
DAO
The main feature of Room is that it generates CURD code based on annotations, reducing the amount of handwritten code.
The Dao is first created with @DAO
@Dao
interface ArticleDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveArticls(vararg articles: Article)
@Query("SELECT * FROM Article")
fun getArticles(a): Flow<List<Article>>
}
Copy the code
@insert, @update, @delete, etc. The @Query method is defined to read information from the database, with the SQL statement of SELECT as its annotation parameter.
The @Query method supports return values of type RxJava or Coroutine Flow, and KAPT generates code based on the return value type. The subscriber automatically receives new data when db data updates result in changes to the Query Observable or Flow result.
Note: LiveData is an Androd platform object, although Room also supports a return value of type LiveData. For an ideal MVVM architecture, the data layer should be Android independent, so using LiveData as the return value type is not recommended
AppDatabase instance
Finally, get the DAO by creating a Database instance
@Database(entities = [Article::class], version = 1) // Define the current version of db and database table (array can define multiple tables)
@TypeConverters(value = [DateTypeConverter::class]) // Define the type converters used
abstract class AppDatabase : RoomDatabase() {
abstract fun articleDao(a): ArticleDao
companion object {
@Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase = instance ? : synchronized(this) { instance ? : buildDatabase(context).also { instance = it } }private fun buildDatabase(context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "ArticleDb")
.fallbackToDestructiveMigration() // Database upgrade policy
.build()
}
}
Copy the code
2. Realm
Realm is a database designed specifically for mobile devices. Unlike other ORM frameworks such as Room, Realm doesn’t rely on SQLite and has its own zero-copy storage engine, which is significantly faster than other ORM frameworks.
Docs.mongodb.com/realm/sdk/a…
Engineering depends on
//root build.gradle
dependencies {
...
classpath "io.realm:realm-gradle-plugin:$realmVersion". }// module build.gradle
apply plugin: 'com.android.application'
apply plugin: 'realm-android'
Copy the code
Entity
Realm requires an Entity to have an empty constructor, so data class definitions cannot be used. Entity must inherit from RealmObject
open class RealmArticle : RealmObject() {
@PrimaryKey
val id: Long = 0L.val author: String = "".val title: String = "".val desc: String = "".val url: String = "".val likes: Int = 0.val updateDate: Date = Date(),
}
Copy the code
In addition to primitives such as integers and strings, Realm also supports storing common object types such as dates, with internal compatibility processing. You can also use custom types in Entity, but make sure that class is also a derived class from RealmObject.
Initialize the
To use a Realm, pass in the Application for initialization
Realm.init(context)
Copy the code
DAO
The key to defining a DAO is to get an instance of a Realm and then open the transaction with executeTransactionAwait to complete the CURD operation internally.
class RealmDao() {
private val realm: Realm = Realm.getDefaultInstance()
suspend fun save(articles: List<Article>) {
realm.executeTransactionAwait { r -> // open a realm transaction
for (article in articles) {
if (r.where(RealmArticle::class.java).equalTo("id", article.id).findFirst() ! =null) {
continue
}
val realmArticle = r.createObject(Article::class.java, article.id) // create object (table)
// save data
realmArticle.author = article.author
realmArticle.desc = article.desc
realmArticle.title = article.title
realmArticle.url = article.url
realmArticle.likes = article.likes
realmArticle.updateDate = article.updateDate
}
}
}
fun getArticles(a): Flow<List<Article>> = callbackFlow { // wrap result in callback flow ``
realm.executeTransactionAwait { r ->
val articles = r.where(RealmArticle::class.java).findAll()
articles.forEach {
offer(it)
}
}
awaitClose { println("End Realm")}}}Copy the code
In addition to getting a Realm configured by default, you can also get instances based on custom configurations
val config = RealmConfiguration.Builder()
.name("default-realm")
.allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.compactOnLaunch()
.inMemory()
.build()
// set this config as the default realm
Realm.setDefaultConfiguration(config)
Copy the code
3. GreenDAO
GreenDao is an open source framework for Android. Like Room, greenDao is a lightweight ORM solution based on SQLite. GreenDAO is optimized for the Android platform and has very little memory overhead at runtime.
Github.com/greenrobot/…
Engineering depends on
//root build.gradle
buildscript {
repositories {
jcenter()
mavenCentral() // add repository
}
dependencies {
...
classpath 'org. Greenrobot: greendao - gradle - plugin: 3.3.0' / / greenDao plug-in. }}Copy the code
//module build.gradle
// Add the GreenDao plugin
apply plugin: 'org.greenrobot.greendao'
dependencies {
//GreenDao dependency is added
implementation 'org.greenrobot:greendao:latest_version'
}
greendao {
// Database version number
schemaVersion 1
// Directory to generate database files
targetGenDir 'src/main/java'
// The package name of the generated database-related files
daoPackage 'com.sample.greendao.gen'
}
Copy the code
Entity
GreenDAO’s Entity definition is similar to Room’s. @Property is used to define the name of the Property in db
@Entity
data class Article(
@Id(assignable = true)
val id: Long.val author: String,
val title: String,
val desc: String,
val url: String,
val likes: Int.@Property(nameInDb = "updateDate")
@Convert(converter = DateConvert::class.java, columnType = String.class)
val date: Date,
)
Copy the code
GreenDAO only supports basic data. Complex types are converted via PropertyConverter
class DateConverter : PropertyConverter<Date, String>{
@Override
fun convertToEntityProperty(value: Integer): Date {
return format.parse(value)
}
@Override
fun convertToDatabaseValue(date: Date): String {
return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(date)
}
}
Copy the code
Generate DAO related files
After defining the Entity, the compilation project will generate three dao-related files in the com.sample.greendao.ge directory that we configured: DaoMaster, DaoSessiion, ArticleDao,
- DaoMaster: Manages database connections, internally holding the database object SQLiteDatabase,
- DaoSession: Each database connection can open multiple sessions, but the session cost is small, and there is no need to repeatedly create a connection
- XXDao: Use DaoSessioin to obtain daOs that access specific XX entities
DaoSession initializes DaoSession as follows:
fun initDao(a){
val helper = DaoMaster.DevOpenHelper(this."test") // The name of the database created
val db = helper.writableDb
daoSession = DaoMaster(db).newSession() // Create DaoMaster and DaoSession
}
Copy the code
Read and write data
// Insert a piece of data of type Article entity class
fun insertArticle(article: Article){
daoSession.articleDao.insertOrReplace(article)
}
// Return all articles
fun getArticles(a): List<Article> {
return daoSession.articleDao.queryBuilder().list()
}
// Find an item of data by name and return List
fun getArticle(name :String): List<Article> {
return daoSession.articleDao.queryBuilder()
.where(ArticleDao.Properties.Title.eq(name))
.list()
}
Copy the code
ArticleDao can be obtained through daoSession, and then conditions can be added through QueryBuilder to adjust the price query.
4.ObjectBox
ObjectBox is a NoSQL database designed for small Internet of Things and mobile devices. It is a key-value storage database, non-column storage, and has more performance advantages in non-relational data storage scenarios. ObjectBox and GreenDAO use a team.
Docs. Objectbox. IO/kotlin – supp…
Engineering depends on
//root build.gradle
dependencies {
...
classpath "io.objectbox:objectbox-gradle-plugin:$latest_version". }// module build.gradle
apply plugin: 'com.android.application'
apply plugin: 'io.objectbox'. dependencies { ... implementation"io.objectbox:objectbox-kotlin:$latest_version". }Copy the code
Entity
@Entity
data class Article(
@Id(assignable = true)
val id: Long.val author: String,
val title: String,
val desc: String,
val url: String,
val likes: Int.@NameInDb("updateDate")
val date: Date,
)
Copy the code
The Entity of an ObjectBox is very similar to greenDAO, except that the name of the individual annotation is different, such as @nameindb instead of @Property
BoxStore
You need to create a BoxStore for your ObjectBox to manage the data
object ObjectBox {
lateinit var boxStore: BoxStore
private set
fun init(context: Context) {
boxStore = MyObjectBox.builder()
.androidContext(context.applicationContext)
.build()
}
}
Copy the code
The creation of BoxStore requires the use of an Application instance
ObjectBox.init(context)
Copy the code
DAO
ObjectBox provides Box objects for entity classes to read and write data from
class ObjectBoxDao() : DbRepository {
// Create a Box instance based on Article
private val articlesBox: Box<Article> = ObjectBox.boxStore.boxFor(Article::class.java)
override suspend fun save(articles: List<Article>) {
articlesBox.put(articles)
}
override fun getArticles(a): Flow<List<Article>> = callbackFlow {
// Convert the query result to Flow
val subscription = articlesBox.query().build().subscribe()
.observer { offer(it) }
awaitClose { subscription.cancel() }
}
}
Copy the code
ObjectBox’s Query can return RxJava results, but if you want to use other forms such as Flow, you need to do your own conversion.
5. SQLDelight
SQLDelight is Square’s open source library that generates type-safe APIS for Kotlin and other platform languages based on SQL statements.
Cashapp. Making. IO/sqldelight /…
Engineering depends on
//root build.gradle
dependencies {
...
classpath "com.squareup.sqldelight:gradle-plugin:$latest_version". }// module build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.squareup.sqldelight'. dependencies { ... implementation"com.squareup.sqldelight:android-driver:$latest_version"
implementation "com.squareup.sqldelight:coroutines-extensions-jvm:$delightVersion". }Copy the code
The sq file
The project structure of DqlDelight is different from other frameworks. You need to create a SRC /main/ SQLDelight directory at the same level of SRC /main/ Java, create a subdirectory according to the package name, and add the.sq file
# Article.sq import java.util.Date; CREATE TABLE Article( id INTEGER PRIMARY KEY, author TEXT, title TEXT, desc TEXT, url TEXT, likes INTEGER, updateDate TEXT as Date ); selectAll: #label: selectAll SELECT * FROM Article; insert: #label: insert INSERT OR IGNORE INTO Article(id, author, title, desc, url, likes, updateDate) VALUES ? ;Copy the code
Adding label to SQL statements in Article. Sq generates the corresponding.kt file articlequeries.kt. The DAO we created was also SQL CURD with ArticleQueries
DAO
First, we need to create a SqlDriver for SQL database connection, transaction management, etc. Android platform needs to pass Context, based on SqlDriver to obtain the ArticleQueries instance
class SqlDelightDao() {
// Create the SQL driver
private val driver: SqlDriver = AndroidSqliteDriver(Database.Schema, context, "test.db")
// Create db instances based on drivers
private val database = Database(driver, Article.Adapter(DateAdapter()))
// Get the ArticleQueries instance
private val queries = database.articleQueries
override suspend fun save(artilces: List<Article>) {
artilces.forEach { article ->
queries.insert(article) // Insert is the label defined in Article. Sq}}override fun getArticles(a): Flow<List<Article>> =
queries.selectAll() // selectAll is the label defined in Article. Sq
.asFlow() // convert to Coroutines Flow
.map { query ->
query.executeAsList().map { article ->
Article(
id = article.id,
author = article.author
desc = article.desc
title = article.title
url = article.url
likes = article.likes
updateDate = article.updateDate
)
}
}
}
Copy the code
Similar to Room’s TypeConverter, SQLDelight provides ColumnAdapter for data type conversions:
class DateAdapter : ColumnAdapter<Date, String> {
companion object {
private val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
}
override fun decode(databaseValue: String): Date = format.parse(databaseValue) ? : Date()override fun encode(value: Date): String = format.format(value)
}
Copy the code
6. Summary
Above briefly introduced the basic use of various databases, more detailed content please go to the official website. Each framework has its own features in Entity definition and DAO generation, but the design goal is the same: less direct manipulation of SQL and more type-safe reading and writing of databases.
Finally, a table summarizes the features of each framework:
| | | | background storage engine RxJava | Coroutine | | attachment file data type | | : — | — – | : — | — – | : — | — – | : — | — – | : – | | Room | Google biological | SQLite support | | | | basic compile-time code + TypeConverter | | Realm | tripartite | Core c + + support | | part support | | free complex type | | GreenDAO | | three parties SQLite | does not support | does not support code generation | | compile-time basic + PropertyConverter | Tripartite | | ObjectBox | Json support | | does not support | | free complex type | | SQLDelight | | three parties SQLite support | | | handwritten. Sq | basic + ColumnAdapter |
For performance comparisons, see the following figure, where the horizontal axis is the amount of data read and written and the vertical axis is the time spent:
The experimental results show that both Room and GreenDAO are based on SQLite with similar performance. GreenDAO performs better in query speed. Realm’s own engine provides high data copy efficiency and no need to map complex objects. ObjectBox as a KV database, performance due to SQL is also expected. The image lacks SQLDelight’s curve, and its actual performance is similar to GreeDAO’s and better than Room’s in query speed.
For space performance, see the figure above (memory usage of 50K records). SQLite databases rely on platform services and have a low memory overhead. GreenDAO has the best memory optimization at runtime. ObjectBox is between SQLite and Realm.
Data sources: proandroiddev.com/android-dat…
Selection Suggestions
The above frameworks are currently under maintenance, and there are many users. We can follow the following principles in selection:
- Room may not have an advantage in terms of performance, but as a son of Google, it is the most compatible with Jetpack and naturally supports coroutines. If your project is only on The Android platform and not sensitive to performance, Room is the first choice.
- SQLDelight is recommended if your project is a KMM or other cross-platform application;
- If you have high performance requirements, then Realm is definitely the better choice;
- If you don’t have too many query criteria, consider the kV-type database ObjectBox. If you only use Android, then stable DataStore is a good choice.