目录
一、基础知识
二、详细见进阶使用
三、快速上手
一、基础知识1、依赖库
implementation 'androidx.room:room-runtime:2.2.5'
implementation 'androidx.room:room-common:2.2.5'
implementation 'androidx.room:room-ktx:2.2.5'
kapt "androidx.room:room-compiler:2.2.5"
2、特性
-
SQL语句高亮(封装原始的sqlite)
-
简单入门,依赖库小(相比greenDao、ObjectBox、Realm等)
-
功能强大
-
数据库监听
-
支持Kotlin协程/RxJava/Guava
不足:性能对比其他优秀数据库框架,并无优势;对于ORM的支持不是很好
3、常用注解
-
@Entity 修饰 数据表实体类(内部至少有一个primaryKey标记的字段)
-
@Database 数据库的抽象类
-
@Dao 用于操作数据表的Dao接口
-
@insert 新增
-
@update
-
@delete
-
@query --- sqlite
-
其他注解:
-
@ignore 注解忽略对应字段,在数据表中的映射
-
@index 索引字段标记
-
@primaryKey 数据表的主键
-
@ForeignKey 数据表的外键,确定entry表之间关系
-
@ColumnInfo 用于数据表字段的映射定义
-
@Embedded 用于数据表字段为外部对象的修饰,不需要那个对象也是@entity
-
@Relation 用于多表关联
-
@Transaction 用于数据库的事务操作标记
-
@DatabaseView 创建虚拟数据表,而非数据库中实际的表数据,可以是多个表的部分拼接的,提高复用
-
@TypeConverter 用于适配转换
数据库设计三大基本原则(三大范式)
-
每列的原子性
-
每列均与主键相关
-
每列均与主键直接相关
-
嵌套类
@embedded,将额外对象的内容字段,添加到当前entry的表内,所以,其字段不可与当前entry字段重复
-
多表联查
-
sql语句返回数据,构建临时bean对象
-
databaseView需要在dataBase的抽象类中@database的views添加,而后可用于在@query中使用
-
升降级
创建migration-->内部处理数据库迁移逻辑
addMigrations,可多个版本迁移处理
附:注意知识点
-
entry必须非private构造函数,字段不能private
-
默认不允许主线程操作,可手动添加
allowMainThreadQueries
-
使用room结合liveData时候,返回数据可能null
-
@embeded的挂载,字段避免重复
-
创建
Dao
可以是interface,也可以abstract class ,但是抽象类的时候,所有函数都必须是抽象函数 -
AS中 java(generated)目录下,可查看插件生成的java代码。dao的操作都是事务性的。
-
外键的使用,注意索引index的优化
-
demo如下,简单的增、删、改、查操作,数据都是写死的,删除只能点一次
Room数据库主要由三个部分组成:
Entity
: 数据实体,一个Entity代表一张数据表DAO
: 在这里定义数据表的操作方法Database
: 数据库
步骤一:使用@Entity创建表
要点1:在类上方添加@Entitiy
注解,并定义数据表名称 要点2:通过@PrimaryKey
来定义主键,@ColumnInfo
来定义列名称,也可以不添加注解@ColumnInfo
,那么列名称就默认是属性的名称。
@Entity(tableName = "db_user") //room数据库的注解标记,数据表entity (tableName="db_user",indices = {@Index(value = "uname",unique = true)})
class DbUser {
@PrimaryKey(autoGenerate = true)
var uid = 0
@ColumnInfo(name = "uname")
var name: String? = null
var city: String? = null
var age = 0
//如此数据表中不会有@Ignore标记的属性字段
@Ignore
var isSingle = false
override fun toString(): String {
return "DbUser{" +
"uid=" + uid +
", name='" + name + '\'' +
", city='" + city + '\'' +
", age=" + age +
", single=" + isSingle +
'}'
}
}
步骤二、使用@Dao,创建操作数据库的接口,定义数据表操作方法
可以看到,在这里通过各种注解实现了数据表的各种增删改查方法。其中delete,并不是说要传入一个完全一样的实体,只要PrimaryKey
匹配就行。具体就不多讲了,一看就懂。
最重要的是要在接口前面添加注解@Dao
@Dao
interface UserDao {
//查询所有数据,若返回liveData则为 LiveData
@Query(value = "select * from db_user")
fun getAll(): List?
@Query("SELECT * FROM db_user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray?): List? //根据uid查询
@Query(
"SELECT * FROM db_user WHERE uname LIKE :name AND "
+ "age LIKE :age LIMIT 1"
)
fun findByName(name: String?, age: Int): DbUser?
@Query("select * from db_user where uid like :id")
fun getUserById(id: Int): DbUser?
@Insert
fun insertAll(vararg users: DbUser?) //支持可变参数
@Delete
fun delete(user: DbUser?) //删除指定的user
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(user: DbUser?) //更新,若出现冲突,则使用替换策略,还有其他策略可选择
}
步骤三、创建数据库,搞一个单例类,获取到数据库对象
最重要的当然是要在类前面添加注解@Database
,在entities
中添加所有数据表的类名,version
表示数据库版本,exportSchema
表示是否生成数据库结构文件,具体可以参考: 处理Schema
要点1. 自定义的Database类必须继承RoomDatabase。 要点2. 在Database中定义获取数据表操作实例的方法。 要点3. 谷歌建议把Database实例定义成单例模式,因为数据库的初始化消耗量相当大,而且也没必要在一个进程中创建多个数据库实例。
@Database(entities = [DbUser::class], version = 1, exportSchema = false)
abstract class UserDatabase : RoomDatabase() {
abstract val userDao: UserDao?
companion object {
const val DB_NAME = "user.db"
private var instance: UserDatabase? = null
@Synchronized
fun getInstance(context: Context?): UserDatabase? {
if (instance == null) {
instance = Room.databaseBuilder(
context!!,
UserDatabase::class.java,
DB_NAME
)
.allowMainThreadQueries() //默认room不允许在主线程操作数据库,这里设置允许
.build()
}
return instance
}
}
}
使用:
class MainActivity : AppCompatActivity() {
private val tvInsert: TextView by lazy { findViewById(R.id.tv_insert_room) }
private val tvDelete: TextView by lazy { findViewById(R.id.tv_delete_room) }
private val tvUpdate: TextView by lazy { findViewById(R.id.tv_update_room) }
private val tvQueryID: TextView by lazy { findViewById(R.id.tv_query_id_room) }
private val tvSize: TextView by lazy { findViewById(R.id.tv_size_room) }
private val tvAll: TextView by lazy { findViewById(R.id.tv_all_room) }
private var instance: UserDatabase? = null
private var userDao: UserDao? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
instance = UserDatabase.getInstance(this)
TODO: 2020/8/29 删除数据库,在测试的时候,清空表,重新建
deleteDatabase(UserDatabase.DB_NAME)
userDao = instance?.userDao
}
fun insert(view: View?) {
val sb = StringBuilder()
var user: DbUser
for (i in 0..4) {
user = DbUser()
user.age = 20 + i
user.city = "北京 $i"
user.name = "小明 $i"
user.isSingle = i % 2 == 0
userDao!!.insertAll(user)
sb.append(user.toString()).append("\n")
}
// userDao.insertAll(users.get(0),users.get(1));
tvInsert.text = sb.toString()
getAll()
}
private fun getAll() {
val all = userDao!!.getAll() ?: return
val sb = StringBuilder()
all.forEach { user ->
sb.append("uid: ")
.append(user?.uid)
.append("姓名: ")
.append(user?.name)
.append("年龄: ")
.append(user?.age)
.append("城市: ")
.append(user?.city)
.append("Single: ")
.append(user?.isSingle)
.append("\n")
}
val text = "All Size : " + all.size
tvSize.text = text
tvAll.text = sb.toString()
}
fun delete(view: View?) {
val user = userDao!!.findByName("小明 " + 3, 23)
userDao!!.delete(user)
//
tvDelete.text = user.toString()
getAll()
}
fun update(view: View?) {
val user = userDao!!.findByName("小明 " + 2, 22) ?: return
user.age = 33
user.city = "上海"
user.name = "张三"
user.isSingle = true
userDao!!.update(user)
tvUpdate.text = user.toString()
getAll()
}
fun queryId(view: View?) {
val userById = userDao!!.getUserById(3)
if (userById != null) {
tvQueryID.text = userById.toString()
} else {
Toast.makeText(this, "id=3 的user查询不到", Toast.LENGTH_SHORT).show()
}
//为了显示操作后的更新数据
getAll()
}
fun queryAll(view: View?) {
getAll()
}
override fun onDestroy() {
super.onDestroy()
instance!!.clearAllTables()
}
}