Android的room数据库使用小结(kotlin)

简介: Android的room数据库使用小结(kotlin)

还在使用原生的sqllite?有这么清爽且稳如狗的room为啥不用呢?


Room是Google官方推荐使用的数据库,相比较某些优秀数据库框架来说,不用过于担心某天库会停止维护,且访问数据库非常流畅,并且提供了与常规的ORM框架一样,通过添加编译期注解来进行表和字段的配置,譬如@Database、@Dao、@Entity、@Query、@Insert、@Update、@Detele等的注解,可以使用简单代码实现相比以前SQLite更复杂的代码的效果,这点儿有点儿类似于java世界里的mybatis。总而言之, Room功能强大,速度和稳定性不弱,还简单易用,算得上是一个优秀的数据库。


这里总结下使用room数据库的过程和遇到的问题,以及如何稳定的进行数据库的迁移和升级。


代码以kotlin为例,java也类似差不多的。


首先准备,引入依赖,在app文件夹下的build.gradle中增加:


 //ROOM数据库
    implementation "android.arch.persistence.room:runtime:1.1.1"
    kapt "android.arch.persistence.room:compiler:1.1.1"


需要注意的是,使用的是kotlin,annotationProcessor  "android.arch.persistence.room:compiler:1.1.1"的写法需换成kapt  "android.arch.persistence.room:compiler:1.1.1"


还需引入一个插件:apply plugin: 'kotlin-kapt'


代码结构目录是不是很清晰,在room下dao的单独建dao包,操作接口在这里实现,所有的表定义单独在entity包中。



接下来最好在 app文件夹下的build.gradle中再增加项配置,让编译后自动输出生成的schemas,里面有创建和修改表结构的sql语句。(这在数据库升级时很有用,可以拷贝过来用)

在app文件夹下的build.gradle的defaultConfig 增加:


javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                        "room.schemaLocation":"$projectDir/schemas".toString(),
                        "room.incremental":"true",
                        "room.expandProjection":"true"]
            }
        }


这样在app文件夹下会多输出个schemas文件夹,里面有对应版本的sql,json文件。在做数据库升级时这很有用,可以复用里面的sql语句。


简单的使用:


第一步,在entiy包中增加实体类的定义,每个实体类对应一个表,,类前面加@Entity注解,默认类名就是最终生成的表名,如果不想让一致,可以指定表名(@Entity (tableName = "users"))。


使用主键 : 一个Entry中至少需要一个主键,使用@PrimaryKey来注释. 自增类型的主键,则可以设置 @PrimaryKey 的 autoGenerate 属性。


忽略字段: 使用@Ignore注解,如,@Ignore val picture: Bitmap?


更改字段名使用@ColumnInfo(name = "xxx")注解,如果不指定,默认就是属性名。


package com.xxx.xx.room.entity
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
@Entity
class User{
    // 自增主键
    @PrimaryKey(autoGenerate = true)
    var id = 0
    var userName: String? = null
    var passWord: String? = null
}


第二步,在dao包里写对应的dao,对应的操作,


注意这些操作接口最好都带个返回值,比如insert返回long,delete返回Int。因为最终的使用总要对操作结果来个判断吧。


如下,对user表的增删改查全部有啦,够简单和清爽吧。


package com.xxx.xx.room.dao
import android.arch.persistence.room.*
import com.xxx.xx.room.entity.User
@Dao
interface UserDao {
    //查询user表中所有数据
    @get:Query("SELECT * FROM user")
    val all: List<User?>?
    @Query("SELECT * FROM user WHERE 'id' IN (:userIds)")
    fun loadAllByIds(userIds: IntArray?): List<User?>?
    @Query("SELECT * FROM User LIMIT 1")
    fun findUser(): User?
    @Insert
    fun insert(user: User?):Long
    @Delete
    fun delete(vararg users: User?):Int
    // 改
    @Update
    fun update(vararg users: User): Int
    @Query("DELETE FROM User")
    fun deleteAllUser()
    @Query("SELECT COUNT(*) FROM User")
    fun countAll():Int
}


Insert还可以开启个对冲突的策略,默认的添加重复的数据(主键一致)会抛异常的。使用 @Insert(onConflict = OnConflictStrategy.REPLACE)重复时则会替换。


第三步,添加room数据库并封装个单例操作类,


//AppDb.kt
package com.xxx.xx.room
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import com.xxx.xx.room.dao.AgeDao
import com.xxx.xx.room.dao.UserDao
import com.xxx.xx.room.entity.Age
import com.xxx.xx.room.entity.User
@Database(entities = [User::class,Age::class], version = 2,exportSchema = true)
abstract class AppDb : RoomDatabase() {
    abstract fun userDao(): UserDao
    abstract fun ageDao(): AgeDao
}


//Dbhelper.kt
package com.xxx.xx.room
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.migration.Migration
import android.content.Context
import android.os.Environment
import java.io.File
class DbHelper {
    var db: AppDb
    var DB_PATH = Environment.getExternalStorageDirectory().absolutePath + File.separator + "xxx" + File.separator+"db"+ File.separator//
    var DB_NAME = DB_PATH +"mydb"
    private constructor(context:Context){
        //判断目录是否存在,不存在则创建该目录
        val dir = File(DB_PATH)
        if (!dir.exists()) {
            dir.mkdirs()
        }
        //允许在主线程中查询
        db = Room.databaseBuilder(context,AppDb::class.java, DB_NAME)
                .allowMainThreadQueries().addMigrations(MIGRATION_1_2).setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
                .build()
    }
    //数据库迁移
    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("CREATE TABLE IF NOT EXISTS `Age` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userAge` TEXT, `age` INTEGER)")
        }
    }
    companion object {
        var context: Context?=null
        private var mInstance: DbHelper? = null
        fun getInstance(): DbHelper {
            if (DbHelper.mInstance == null) {
                synchronized(DbHelper::class.java) {
                    if (DbHelper.mInstance == null) {
                        DbHelper.mInstance = DbHelper(context!!)
                    }
                }
            }
            return DbHelper.mInstance!!
        }
    }
}


最后就可以愉快的使用啦:


...
var userDao: UserDao
userDao = DbHelper.getInstance().db.userDao()
var user = User()
user.userName = "yang"
user.passWord="123456"
userDao.insert(user)
...


当修改表字段或者增加表结构时,数据库升级注意事项:


无论是增加新表还是只是修改表字段或增加表字段,都需要增加下数据库的版本号并增加Migration处理,


@Database(entities = [User::class,Age::class], version = 2,exportSchema = true)


  db = Room.databaseBuilder(context,AppDb::class.java, DB_NAME)
        .allowMainThreadQueries().addMigrations(MIGRATION_1_2).setJournalMode(RoomDatabase.JournalMode.TRUNCATE).build()
    //数据库迁移
    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("CREATE TABLE IF NOT EXISTS `Age` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userAge` TEXT, `age` INTEGER)")
        }
    }


如果不更改version还增加了表结构或修改了表字段,则会crash,报java.lang.IllegalStateException: 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.


如果只增加version没有对应的Migration,同样会crash,


虽然有不提供自定义Migration,又不想引发crash的fallbackToDestructiveMigration方法,但非常不建议这么搞,要去对这些表结构的变化做处理,考虑到数据的安全性。


如果仅是测试,清空数据或删除掉db文件就可以从新来过了。


如果有正式的数据,在进行表结构的更改前,需做好安全测试保证数据不丢失。


多表关联,因为SQLite是关系型数据库, 你可以指定对象之间的关系. 尽管大多数对象关系的映射允许实体对象引用彼此, 而Room却显式地禁止了这个特性。


尽管不能使用直接的对象关系, Room仍然允许在实体之间定义外键约束。比如


@ForeignKey(entity = OrderTransdtlRecord::class, parentColumns = ["billno"],childColumns = ["billno"], onDelete = CASCADE, onUpdate = CASCADE)

有些时候, 在数据库逻辑中, 你想将一个实体或者POJO表示为一个紧密联系的整体, 即使这个对象包含几个域. 在这些情况下, 你能够使用@Embedded注解来表示一个对象, 而你想将这个对象分解为表内的子域. 然后你可以查询这些嵌套域, 就像你查询其它的独立列一样。


相关文章
|
1月前
|
设计模式 Android开发 Kotlin
Android经典实战之Kotlin委托模式和by关键字
本文介绍了Kotlin中`by`关键字在类及属性委托中的运用,通过实例展示了如何利用类委托简化接口实现,以及如何借助标准与自定义属性委托管理属性的读写操作。通过`by`关键字的支持,Kotlin使得委托模式的实现更为直观且高效。
45 4
|
1月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
33 2
|
1月前
|
缓存 数据处理 Android开发
Android经典实战之Kotlin常用的 Flow 操作符
本文介绍 Kotlin 中 `Flow` 的多种实用操作符,包括转换、过滤、聚合等,通过简洁易懂的例子展示了每个操作符的功能,如 `map`、`filter` 和 `fold` 等,帮助开发者更好地理解和运用 `Flow` 来处理异步数据流。
77 4
|
11天前
|
Android开发 Kotlin
Android经典面试题之Kotlin的==和===有什么区别?
本文介绍了 Kotlin 中 `==` 和 `===` 操作符的区别:`==` 用于比较值是否相等,而 `===` 用于检查对象身份。对于基本类型,两者行为相似;对于对象引用,`==` 比较值相等性,`===` 检查引用是否指向同一实例。此外,还列举了其他常用比较操作符及其应用场景。
167 93
|
1月前
|
编译器 API Android开发
Android经典实战之Kotlin Multiplatform 中,如何处理不同平台的 API 调用
本文介绍Kotlin Multiplatform (KMP) 中使用 `expect` 和 `actual` 关键字处理多平台API调用的方法。通过共通代码集定义预期API,各平台提供具体实现,编译器确保正确匹配,支持依赖注入、枚举类处理等,实现跨平台代码重用与原生性能。附带示例展示如何定义跨平台函数与类。
67 0
|
18天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
46 6
|
15天前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
31 1
|
19天前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
32 4
|
30天前
|
算法 安全 数据安全/隐私保护
Android经典实战之常见的移动端加密算法和用kotlin进行AES-256加密和解密
本文介绍了移动端开发中常用的数据加密算法,包括对称加密(如 AES 和 DES)、非对称加密(如 RSA)、散列算法(如 SHA-256 和 MD5)及消息认证码(如 HMAC)。重点讲解了如何使用 Kotlin 实现 AES-256 的加密和解密,并提供了详细的代码示例。通过生成密钥、加密和解密数据等步骤,展示了如何在 Kotlin 项目中实现数据的安全加密。
57 1
|
1月前
|
算法 安全 数据安全/隐私保护
Android经典实战之常见的移动端加密算法和用kotlin进行AES-256加密和解密
本文介绍了移动端开发中常用的数据加密算法,包括对称加密(如 AES 和 DES)、非对称加密(如 RSA)、散列算法(如 SHA-256 和 MD5)及消息认证码(如 HMAC)。重点展示了如何使用 Kotlin 实现 AES-256 的加密和解密,提供了详细的代码示例。
33 2