JetPack之Room使用介绍

文章目录

1.导入依赖

  apply plugin: 'kotlin-kapt'
  
  implementation "androidx.room:room-runtime:2.2.6"
  kapt "androidx.room:room-compiler:2.2.6"
  //room扩展库 支持通过协程操作数据库
  implementation "androidx.room:room-ktx:2.2.6"
  //数据库测试
  testImplementation "androidx.room:room-testing:2.2.6"

2.使用实体类定义数据

Address类

data class Address (
    val street:String?,
    val state:String?,
    val city:String?
        )

User类

@Entity
data class User(
    @PrimaryKey(autoGenerate = true) 
    val uid:Int,
    
    @ColumnInfo(name="first_name") 
    val firstName:String?,
    
    @ColumnInfo(name = "last_name")
    val lastName:String?,
    
    @Embedded
    val address: Address?
)

Book基类

open class BaseBook{
    val time:Long=0
}

子类Book

@Entity(tableName = "lkBook",primaryKeys = ["lkBookName"],indices = [Index(value = ["author"])],ignoredColumns = ["time"])
data class Book (
    val id : Int,

    val author :String,

    @ColumnInfo(name = "lkBookName")
    val name :String,

    val country:String?,
    ):BaseBook()
  • Entity :用于将该类映射到数据库中的一张表
  • tableName:不设置则默认使用类名作为表名,否则指定对应的表名
  • PrimaryKey:指定主键,每个实体必须至少有一个字段定义为主键,如果分配自动ID,设置@PrimaryKey 的 autoGenerate 属性,设置多个主键可以通过primaryKeys
  • ColumnInfo:为实体类中的字段设置自定义的名称
  • indices:给数据库某些列添加索引
    1.索引不是越多越好
    2.不要对经常变动数据加索引
    3.小数据量的表不需要加索引
    4.索引一般加在常用来查询的字段上
  • ignoredColumns:该字段不会被映射到表中
  • Embedded:用于将某个实体或数据对象表示为一个紧密的整体
  • Transaction:确保整个操作以原子方式执行

3.使用DAO访问数据

  • 当查询语句中需要传集合或者数组时可以通过如下方式指定(:字段名)
 @Query("select * from user where uid  in (:userIds)")
 fun loadAllByIds(userIds:IntArray):Flow<List<User>>
  • 当查询语句中传入某个字段通过如下方式指定:字段名
    @Query("select * from user where first_name like :first and last_name like :last limit 1")
    suspend fun findByName(first:String,last:String):User
  • 在插入数据时可以指定冲突策略,默认是OnConflictStrategy.ABORT
    1.OnConflictStrategy.REPLACE:冲突策略是取代旧数据同时继续事务
    2.OnConflictStrategy.ROLLBACK:冲突策略是回滚事务
    3.OnConflictStrategy.ABORT:冲突策略是终止事务
    4.OnConflictStrategy.FAIL:冲突策略是事务失败
    5.OnConflictStrategy.IGNORE:冲突策略是忽略冲突

    策略说明链接

@Dao
interface UserDao {
    @Query("select * from user")
    fun getAll():Flow<List<User>>

    @Query("select * from user where uid  in (:userIds)")
    fun loadAllByIds(userIds:IntArray):Flow<List<User>>

    @Query("select * from user where first_name like :first and last_name like :last limit 1")
    suspend fun findByName(first:String,last:String):User

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(vararg users:User)

    @Delete
    suspend fun delete(user:User)

    @Update
    suspend fun updateUser(user:User)

    @Query("delete from user")
    suspend fun deleteAll()

}

UserRepository.kt

class UserRepository(private val userDao:UserDao) {

    fun loadAllUser(): Flow<List<User>?>{
        return userDao.getAll()
    }

    fun loadAllByIds(userIds:IntArray): Flow<List<User>> {
        return userDao.loadAllByIds(userIds)
    }

    suspend fun insertAll(vararg users:User){
        userDao.insertAll(*users)
    }


    suspend fun deleteAll(){
        userDao.deleteAll()
    }
}

UserViewModel.kt

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    val result by lazy {
        MutableLiveData<List<User>?>()
    }

    fun insert(user: User) = viewModelScope.launch {
        repository.insertAll(user)
    }

    fun updateUser(user: User) = viewModelScope.launch {
        repository.update(user)
        getAll()
    }

    fun deleteAll() = viewModelScope.launch {
        repository.deleteAll()
        getAll()

    }

    fun loadAllByIds(userIds: IntArray): LiveData<List<User>> =
        repository.loadAllByIds(userIds).asLiveData()


    fun getAll(){
       viewModelScope.launch(Dispatchers.Main) {
           repository.loadAllUser().collect {
               result.value = it
           }
       }
    }

}


class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

AppDatabase.kt

@Database(entities = [User::class,Book::class],version = 1,exportSchema = false)
abstract class AppDataBase : RoomDatabase(){

    abstract fun userDao():UserDao

    private class UserDatabaseCallback(private val scope:CoroutineScope):RoomDatabase.Callback(){
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                scope.launch {
                    val userDao = database.userDao()
                    userDao.deleteAll()

                    userDao.insertAll(
                        User(1,"张","三",null),
                        User(2,"李","四",null),
                        User(3,"王","五",Address("祥符街道","拱墅区","杭州市"))
                    )
                }
            }
        }
    }

    companion object{
        @Volatile
        private var INSTANCE: AppDataBase? = null

        fun getDatabase(context: Context,scope: CoroutineScope): AppDataBase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDataBase::class.java,
                    "lkDatabase"
                ).addCallback(UserDatabaseCallback(scope)).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

App.kt

class App:Application() {
    val applicationScope = CoroutineScope(SupervisorJob())
    val database by lazy { AppDataBase.getDatabase(this,applicationScope) }
    val respository by lazy { UserRepository(database.userDao()) }

    override fun onCreate() {
        super.onCreate()
    }

}

4.进行数据库迁移

迁移步骤:
1.修改AppDataBase中的version+1
2.定义一个Migration,比如

  private val migrate_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL(
                    """
                    CREATE TABLE new_Student (
	uid INTEGER PRIMARY KEY NOT NULL,
	first_name TEXT,
	last_name TEXT,
	street TEXT,
	state TEXT,
	city text,
	birthday INTEGER NOT NULL DEFAULT 0
)
                """.trimIndent()
                )

                database.execSQL(
                    """
                   INSERT INTO new_Student(uid,first_name,last_name,street,state,city)
                   SELECT uid,first_name,last_name,street,state,city FROM Student
                   
                """.trimIndent()
                )

                database.execSQL("drop table Student")
                database.execSQL("alter table new_Student rename to Student")
            }

        }


        private val migrare_2_3 = object :Migration(2,3){
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE President ADD  birthday INTEGER")
            }
        }

3.增加迁移字段

@Entity
data class Student(
    .....
    
    @ColumnInfo(defaultValue = "0")
    val birthday:Int,
)

@Entity
data class President (
    .....
    
    val birthday:Int?
)

4.通过RoomDatabase的addMigrations添加

 .addMigrations(migrate_1_2, migrare_2_3)

5.关于数据库迁移的几个错误

1.给某张表新增字段时,新增字段必须放在最后面,然后通过
val birthday:Int?

 database.execSQL("ALTER TABLE President ADD birthday INTEGER")

同时要注意字段是否可空,默认执行的sql语句是可空的, 如果新增的字段没有加?会报错;或者说如果是非空字段--val birthday:Int, 那么执行的sql语句需要添加 NOT NULL

 database.execSQL("ALTER TABLE President ADD birthday INTEGER NOT NULL")

2.如果在表中新增的字段想指定位置,在mysql中是可以做到的,通过after指定位于哪个字段之后,但是在sqlite中无法实现,只能通过创建一张新表,然后把在把数据从新插入,在把老表删除,新的表名在重新命名成老的表名,也就是上诉中的migrate_1_2

database.execSQL("ALTER TABLE President ADD COLUMN gender INTEGER NOT NULL AFTER presidentId")

3.如果碰到执行的SQL语句报错的时候,如果你认为你的写法没有错,一定要把SQL语句单独拿出来测试一下,因为有的时候可能sqlite3确实不支持,在mysql上就可以,就比如上诉的after!!!。

上一篇:想跳槽涨薪的必看!字节跳动移动架构师学习笔记,成功入职阿里


下一篇:深入浅出!微信小程序的事件处理,顺利通过阿里Android岗面试