1、导入room库
项目app.gradle导入
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
// 如果编译用kapt 导入这个
kapt "androidx.room:room-compiler:$room_version"
// 如果编译用ksp导入这个
ksp "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
2、Room 包含 3 个主要组件:
-
数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 [@Database`注释的类应满足以下条件:
- 是扩展
RoomDatabase
的抽象类。 - 在注释中添加与数据库关联的实体列表。
- 包含具有 0 个参数且返回使用
@Dao
注释的类的抽象方法。
在运行时,您可以通过调用
Room.databaseBuilder()
获取Database
的实例。 - 是扩展
Entity:表示数据库中的表。
DAO:包含用于访问数据库的方法。
例如:
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
注意:如果您的应用在单个进程中运行,在实例化 AppDatabase
对象时应遵循单例设计模式。每个 RoomDatabase
实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()
。这样,如果您在每个进程中都有一个 AppDatabase
实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中 AppDatabase
的实例。
定义实体类
@Entity(tableName = "user")
class User {
@PrimaryKey(autoGenerate = true)
var uid: Int? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Ignore
var memo: String = ""
}
必须要有主键,可以使用@PrimaryKey (autoGenerate = true) ,autoGenerate表示自动生成。 @Ignore表示不保存这个字段
使用DAO访问数据
Insert
@Dao
interface UserDao {
@Insert
fun insertUser(user: User):Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
}
如果 @Insert
方法只接收 1 个参数,则它可以返回 long
,这是插入项的新 rowId
。如果参数是数组或集合,则应返回 long[]
或 List
。
onConflict = OnConflictStrategy.REPLACE,表示如已有数据,就覆盖掉。数据的判断通过主键进行匹配,也就是id,非整个EquipType对象。
Update
@Dao
interface UserDao {
@Update
fun updateUser(user: User)
@Update
fun updateUsers(vararg user: User)
}
update根据数据的主键去判断和更新数据
Delete
@Dao
interface UserDao {
@Delete
fun deleteUser(user:User)
@Delete
fun deleteUsers(vararg user:User)
}
delete根据数据的主键去判断和删除数据
Query
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
}
可以在sql语句中使用冒号:去引用方法传递过来的参数。可以传递多个参数,也可以传递集合。
如果一个表字段很多,可以创建一个新的数据类,注明需要的参数,例如:
data class NameTuple(
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
@Dao
interface UserDao {
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List
}
使用流进行响应式查询
场景 如果有一个用户列表界面,添加了一个用户,需要刷新列表,可以使用协程的Flow,Room的查询可以返回Flow,一旦数据有变化,会重新触发一次查询,返回新的数据。
interface UserDao {
@Query("SELECT * FROM user")
fun getAll():Flow >
}
lifecycleScope.launch() {
val list = MyApplication.instance().db.userDao()
.getAll()
list.collect(object : FlowCollector> {
override suspend fun emit(value: List) {
for (user in value) {
Log.d("haha", user.uid.toString() + ":" + user.firstName)
}
}
})
}
getAll先会查询一次数据,然后每次表数据变更,都会重新触发一次getAll的数据查询。
使用ViewModel+LiveData
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll():LiveData >
}
class OneFragmenModel : ViewModel() {
val list: LiveData> = MyApplication.instance().db.userDao().getAll()
}
oneFragmenModel.list.observe(viewLifecycleOwner,Observer>{
for (user in it){
Log.d("hahaha",user.uid.toString()+" "+user.firstName)
}
})
使用事务
使用注解@Transaction,可以实现事务
@Dao
interface UserDao {
@Delete
fun delete(user: User)
@Transaction
fun deleteAndInsertUser(user:User){
delete(user)
insertUser(user)
}
@Insert
fun insertUser(user: User):Long
}
定义对象之间的关系
Room不允许对象互相引用,但是可以定义几种关系
1、创建嵌套对象
在数据库逻辑中将某个实体或数据对象表示为一个紧密的整体,即使该对象包含多个字段也是如此。在这些情况下,您可以使用 @Embedded
注释表示要分解为表格中的子字段的对象。然后,可以像查询其他各个列一样查询嵌套字段。
@Database(entities = arrayOf(User::class,Address::class), version = 3)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Int? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class Address {
@PrimaryKey(autoGenerate = true)
var addressId: Int? = null
@ColumnInfo(name = "address_name")
var addressName: String = ""
}
数据存储结构,实际上在user表里面增加了列,去存储嵌套对象,会忽略嵌套对象的主键。
2、定义一对一关系
比如一个人有一个身份证
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Long? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class IDCard {
@PrimaryKey(autoGenerate = true)
var id: Int? = null
@ColumnInfo
var idNum: String? = null
@ColumnInfo
var userId: Long? = null
}
如果需要查询人和身份证一起查询出来,需要建立一个新的类,对2个实体进行关联。注意@Embedded不要掉了。
data class UserAndIDCard(
@Embedded
val user: User,
@Relation(
parentColumn = "uid",
entityColumn = "userId"
)
val idCard: IDCard
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndIDCard(): List
}
如果要一起插入,好像是不行的,我用这个关联关系的类,直接用@Insert,是会报错的。目前只能2个表分开单独插入 例如
val userId = MyApplication.instance().db.userDao().insertUser(user)
val idCard = IDCard();
idCard.idNum = "12312412312"
idCard.userId = userId;
MyApplication.instance().db.IDCardDao().insertIDCard(idCard)
3、定义一对多
一个用户有多本书
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Long? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class Book {
@PrimaryKey(autoGenerate = true)
var bookId: Long? = null
@ColumnInfo(name = "book_name")
var bookName = ""
@ColumnInfo(name = "user_id")
var userId :Long? = null
}
data class UserAndBooks(
@Embedded val user: User,
@Relation(
parentColumn = "uid",
entityColumn = "user_id"
)
val bookList: List
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT *FROM User")
fun getUsersAndBooks():List
}
val userId = MyApplication.instance().db.userDao().insertUser(user)
val book1 = Book();
book1.bookName = "book1"
book1.userId = userId
val book2 = Book();
book2.bookName = "book2"
book2.userId = userId
MyApplication.instance().db.bookDao().insertBook(book1)
MyApplication.instance().db.bookDao().insertBook(book2)
val list = MyApplication.instance().db.userDao().getUsersAndBooks();
定义多对多
一首歌可以在多个播放列表,一个播放列表可以有多首歌
多对多关系与其他关系类型均不同的一点在于,子实体中通常不存在对父实体的引用。因此,需要创建第三个类来表示两个实体之间的关联实体(即交叉引用表)。交叉引用表中必须包含表中表示的多对多关系中每个实体的主键列。
@Entity
class Song {
@PrimaryKey(autoGenerate = true)
var songId: Long? = null
@ColumnInfo(name = "song_name")
var songName = ""
}
@Entity
class PlayList {
@PrimaryKey(autoGenerate = true)
var playlistId : Long? = null
@ColumnInfo(name = "playlist_name")
var playListName =""
}
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)
查询播放列表和每个播放列表所含歌曲的列表,创建一个新的数据类,其中包含单个 Playlist 对象,以及该播放列表所包含的所有 Song 对象的列表
查询歌曲和每首歌曲所在播放列表的列表,创建一个新的数据类,其中包含单个 Song 对象,以及包含该歌曲的所有 Playlist 对象的列表。
两种情况下,都可以通过以下方法在实体之间建立关系:在上述每个类中的 Relation
注释中使用 associateBy
属性来确定提供 Playlist
实体与 Song
实体之间关系的交叉引用实体。不用网上说的entity 那样只能查出一个数据
data class PlaylistWithSongs(
@Embedded val playlist: PlayList,
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val songs: List
)
data class SongWithPlaylists(
@Embedded val song: Song,
@Relation(
parentColumn = "songId",
entityColumn = "playlistId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val playlists: List
)
测试
val playList1 = PlayList()
playList1.playListName = "播放列表1"
val playList2 = PlayList()
playList2.playListName = "播放列表2"
val playList1Id = MyApplication.instance().db.playListDao().addPlayList(playList1)
val playList2Id = MyApplication.instance().db.playListDao().addPlayList(playList2)
val song1 = Song()
song1.songName = "歌曲1"
val song2 = Song()
song2.songName = "歌曲2"
val song1Id = MyApplication.instance().db.songDao().addSong(song1)
val song2Id = MyApplication.instance().db.songDao().addSong(song2)
val playlistSongCrossRef1 = PlaylistSongCrossRef(playList1Id,song1Id)
val playlistSongCrossRef2 = PlaylistSongCrossRef(playList1Id,song2Id)
val playlistSongCrossRef3 = PlaylistSongCrossRef(playList2Id,song1Id)
val playlistSongCrossRef4 = PlaylistSongCrossRef(playList2Id,song2Id)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef1)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef2)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef3)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef4)
val playlistWithSongs =
MyApplication.instance().db.playListDao().getPlaylistsWithSongs()
@Dao
interface PlayListDao {
@Transaction
@Query("SELECT * FROM PlayList")
fun getPlaylistsWithSongs(): List
@Insert
fun addPlayList(playList: PlayList):Long
}
预填充数据库
1、从assets目录
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
2、从File
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
数据库迁移
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).addMigrations(MIGRATION_10_11).build()
val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
}
}
使用 Room 引用复杂数据
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time?.toLong()
}
}
@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
@Entity
data class User(private val birthday: Date?)
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
fun findUsersBornBetweenDates(from: Date, to: Date): List
}
数据库里面存储的就是long