Android JetPack Room

Android JetPack Room,第1张

JetPack Room
      • 1. Room 的优势
      • 2. 主要组件
      • 3. 简单使用介绍
        • 3.1 导包
        • 3.2 User实体类,UserDao接口,UserDataBase
      • 4. 数据库的升级
        • 4.1 直接升级
        • 4.2 Migration方式,手动迁移
        • 4.3 autoMigrations方法,自动迁移
      • 5. exportSchema数据库升级测试记录
      • 6. 监听表数据变化
        • 6.1 LiveData方式
        • 6.2 Flow流的方式
        • 6.3 Rxjava的方式
      • 7. 引用复杂数据
      • 8. 预填充 Room 数据库
        • 8.1 从应用资源中填充
        • 8.2 从文件系统预填充
      • 示例代码

1. Room 的优势

Room是JetPack的一种关系型数据库,Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:

  • 针对 SQL 查询的编译时验证。
  • 可最大限度减少重复和容易出错的样板代码的方便注解。
  • 简化了数据库迁移路径。
2. 主要组件

Room 包含三个主要组件:

  • Entity:实体类,对应的是数据库的一张表结构,使用注解@Entity标记
  • Dao:包含访问一系列访问数据库的方法,使用注解@Dao标记
  • DataBase:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。使用注解@Database标记,另外需要满足以下条件:定义的类必须继承于RoomDatabase的抽象类,在注解中需要定义与数据库相关联的实体类列表。包含一个没有参数的抽象方法并且返回一个Dao对象。

3. 简单使用介绍 3.1 导包
 	implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //room包
    implementation "androidx.room:room-runtime:2.4.0-alpha04"
    kapt "androidx.room:room-compiler:2.4.0-alpha04"
    testImplementation "androidx.room:room-testing:2.4.0-alpha04"
    //room使用协程
    implementation "androidx.room:room-ktx:2.2.4"

    //协程
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
    //lifecycle
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    //工具库
    implementation "com.blankj:utilcodex:1.29.0"
    //gson
    implementation "com.google.code.gson:gson:2.8.7"
3.2 User实体类,UserDao接口,UserDataBase

User实体类定义:

@Entity(tableName = "user")//表名
class User {

    @Ignore //忽略的构造方法
    constructor(id: Int) {
        this.id = id
    }


    @Ignore
    constructor(firstName: String?, age: Int) {
        this.firstName = firstName
        this.age = age
    }


    constructor(id: Int, firstName: String?, age: Int) {
        this.id = id
        this.firstName = firstName
        this.age = age
    }


    @PrimaryKey(autoGenerate = true)//自增长的主键
    @ColumnInfo(name = "id")
    var id: Int = 0

    //typeAffinity,指定类型,name指定行名称
    @ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
    var firstName: String? = null

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0


}

UserDao *** 作接口类

@Dao //注解Dao
interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)//增

    @Delete
    fun deleteUser(user: User)//删除

    @Update
    fun updateUser(user: User)//修改


    @Query("SELECT * FROM user")
    suspend fun getAllUser(): List<User>//添加suspend关键字,异步 *** 作


    @Query("SELECT * FROM user")
    fun getAllUserFlow(): Flow<List<User>>//表数据变化时,返回Flow


    @Query("SELECT * FROM user")
    fun getAllUserLiveData(): LiveData<List<User>?>//表数据变化时,返回一个LiveData


}

UserDataBase类

@Database(
    entities = [User::class],//指定表
    version = 1,//版本
    exportSchema = false//版本升级是否记录到本地,暂时为false
)
abstract class UserDataBase : RoomDatabase() {


    companion object {


        private var INSTANCE: UserDataBase? = null
		//初始化,这里要采用单例,不然会有坑(表数据变化时,可能会监听不了)
        fun init(context: Context) {
            if (INSTANCE == null) {
                synchronized(UserDataBase::class.java) {
                    if (INSTANCE == null) {
                        create(context)
                    }
                }
            }
        }

        private fun create(context: Context) {
            INSTANCE =
                Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
                    .allowMainThreadQueries()//可以在主线程执行查询,不建议这么做
                    .fallbackToDestructiveMigration()//数据库改变时,强制升级时,不报错
                    .build()

        }


        fun getDB(): UserDataBase {
            return INSTANCE ?: throw NullPointerException("UserDataBase 未初始化")
        }



    }
   


    //增加抽象方法,Room框架会自动实现这个方法
    abstract fun getUserDao(): UserDao


}

增删改查 *** 作:

//在Application中实例化UserDataBase
UserDataBase.init(this)

//insert,插入三个数据
  	val user1 = User("小明", 12)
    val user2 = User("小红", 18)
    val user3 = User("小清", 20)
    UserDataBase.getDB().getUserDao().insertUsers(user1, user2, user3)

//删除id为3的User
     val user = User(3)
     UserDataBase.getDB().getUserDao().deleteUser(user)

//修改,将第一个数据修改名称与age
  val user = User(1, "大明明3333", 15)
  UserDataBase.getDB().getUserDao().updateUser(user)

//查询
   			flow {//用Flow查询
                emit(UserDataBase.getDB().getUserDao().getAllUser())
            }.flowOn(Dispatchers.IO)
                .onEach {//数据更新
                    mAdapter.updateData(it.toMutableList())
                }.launchIn(lifecycleScope)

可以看到, *** 作非常简单,代码简洁清晰。

4. 数据库的升级

比如上面的User表,想在表中增加一行city,String类型怎么办?

4.1 直接升级

修改User的属性

@Entity(tableName = "user")
class User {

    @Ignore
    constructor(id: Int) {
        this.id = id
    }


    @Ignore
    constructor(firstName: String?, age: Int) {
        this.firstName = firstName
        this.age = age
    }


    constructor(id: Int, firstName: String?, age: Int) {
        this.id = id
        this.firstName = firstName
        this.age = age
    }


    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Int = 0

    @ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
    var firstName: String? = null

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0

    //增加城市这一行
    var city: String? = null



}

将UserDataBase的version修改为2,

@Database(
    entities = [User::class],
    version = 2,//版本修改为2
    exportSchema = false
)
abstract class UserDataBase : RoomDatabase() {...
                                              
//同时fallbackToDestructiveMigration一定要加上
  Room.databaseBuilder(context, UserDataBase::class.java, "user_db"
                    .allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .build()

这种方式好处是暴力快速,坏处是会清除以前数据库中的数据,不建议用这种方式。

4.2 Migration方式,手动迁移
//自定义版本1到2要修改的地方
private val Migration_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //这里加入了一列
                database.execSQL("ALTER TABLE user ADD COLUMN city TEXT")
            }

        }

//版本1到2添加addMigrations
     INSTANCE =  Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
                    .addMigrations(Migration_1_2)//加上版本的改变
                    .allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .build()

这种方式好处是会保留以前的数据,不好的地方是要自己写sql语句,有些sql语句比较复杂,而且很容易写错。那有没有更好的方法呢?

4.3 autoMigrations方法,自动迁移

注意:Room 在 2.4.0-alpha01 及更高版本中支持自动迁移。如果您的应用使用的是较低版本的 Room,则必须[手动定义迁移]。

@Database(
  version = 2,
  entities = [User::class],
  autoMigrations = [
    AutoMigration (from = 1, to = 2)//在上面的数据库修改中,只需要这一句,就可将表增加一列city,并保留以前的数据
  ]
)
abstract class UserDataBase : RoomDatabase() {
  ...
}

注意:Room 自动迁移依赖于为旧版和新版数据库生成的数据库架构。如果exportSchema设为false,或者如果您尚未使用新版本号编译数据库,自动迁移将会失败。

5. exportSchema数据库升级测试记录
@Database(
    entities = [User::class],
    version = 5,
    exportSchema = true,//将此设置为true
    autoMigrations = [ AutoMigration(from = 1, to = 2),
                       AutoMigration(from = 2, to = 3),
						AutoMigration(from = 3, to = 4), 
                      AutoMigration(from = 4, to = 5)]
)
abstract class UserDataBase : RoomDatabase() {
	...
}

build.gradle中的defaultConfig设置


defaultConfig {
        applicationId "com.hjq.room"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        //Room版本升级记录
        javaCompileOptions {
            annotationProcessorOptions {
                arguments += ["room.schemaLocation":
                                      "$projectDir/schemas".toString()]//记录保存的地方
            }
        }
    }

运行程序后,会在项目目录下多一个schemas文件夹,

可以记录下来数据库版本升级的修改,方便维护与测试。

例如1.json如下:

{
  "formatVersion": 1,
  "database": {
    "version": 1,
    "identityHash": "51f493557826d6594d5cb9ea8939e231",
    "entities": [
      {
        "tableName": "user",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT, `age` INTEGER NOT NULL)",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "firstName",
            "columnName": "first_name",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "age",
            "columnName": "age",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": true
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '51f493557826d6594d5cb9ea8939e231')"
    ]
  }
}
6. 监听表数据变化 6.1 LiveData方式
//UserDao增加一个接口
@Query("SELECT * FROM user")
    fun getAllUserLiveData(): LiveData<List<User>?>
        //LiveData监听数据改变,当表执行了Insert,Update,Delete *** 作后,会打印data change
        mViewModel.getUserLiveData().observe(this) { t ->
            LogUtils.d("data change")
            if (!t.isNullOrEmpty()) {
                mAdapter.updateData(t.toMutableList())            }
        }
6.2 Flow流的方式
//UserDao增加一个接口
  @Query("SELECT * FROM user")
    fun getAllUserFlow(): Flow<List<User>>

   //Flow监听表数据改变,当表执行了Insert,Update,Delete *** 作后,会打印data change
        mViewModel.getUserFlow().onEach {
            LogUtils.d("data change")
            mAdapter.updateData(it.toMutableList())
        }.launchIn(lifecycleScope)
6.3 Rxjava的方式

感兴趣的同学可以查看官网。

7. 引用复杂数据

比如在上面的User中要引入一个UserPosition数据

class UserPosition {
    //经度
    var latitude: Double = 0.0

    //纬度
    var longitude: Double = 0.0

    //海拔
    var altitude: Double = 0.0
}
//在User类中增加一行,这时需要指定类型转换类,实际是userPosition,但存在数据库中确是其他格式,
//为什么要做成这样呢?为了数据安全,避免表结构冲突
 @ColumnInfo(name = "user_position")
    var userPosition: UserPosition? = null
//增加转换类,存储时将UserPosition转成Json,获取时则将Json转成UserPosition
object UserPositionConverter {

    @TypeConverter
    fun objectToString(value: String?): UserPosition? {
        try {
            return Gson().fromJson(value, UserPosition::class.java)
        } catch (e: Exception) {
            LogUtils.d("e===$e")
            e.printStackTrace()
        }
        return null
    }

    @TypeConverter
    fun stringToObject(deviceEventsBean: UserPosition?): String {
        try {
            return Gson().toJson(deviceEventsBean)
        } catch (e: Exception) {
            LogUtils.d("e===$e")
            e.printStackTrace()
        }
        return ""
    }
}
@Entity(tableName = "user")
@TypeConverters(UserPositionConverter::class)//添加转换类
class User {

    @Ignore
    constructor(id: Int) {
        this.id = id
    }


    @Ignore
    constructor(firstName: String?, age: Int) {
        this.firstName = firstName
        this.age = age
    }


    constructor(id: Int, firstName: String?, age: Int) {
        this.id = id
        this.firstName = firstName
        this.age = age
    }


    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var id: Int = 0

    @ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
    var firstName: String? = null

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0

    //增加城市这一行
    var city: String? = null


    @ColumnInfo(name = "user_position")
    var userPosition: UserPosition? = null


}
8. 预填充 Room 数据库

有时,您可能希望应用启动时数据库中就已经加载了一组特定的数据。这称为预填充数据库。 在 Room 2.2.0 及更高版本中,您可以使用 API 方法在初始化时用设备文件系统中预封装的数据库文件中的内容预填充 Room 数据库。

注意:[内存中 Room 数据库]不支持使用 [createFromAsset()] 或 [createFromFile()]预填充数据库。

8.1 从应用资源中填充

如需从位于应用 assets/ 目录中的任意位置的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder 对象调用 createFromAsset() 方法,然后再调用 build()

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build()
8.2 从文件系统预填充

如需从位于设备文件系统任意位置(应用的 assets/ 目录除外)的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder 对象调用 createFromFile() 方法,然后再调用 build()

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromFile(File("mypath"))
    .build()
示例代码

代码

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/801394.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存