啥也不说了,Room真香!
def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
class Movie() : BaseObservable() {
@PrimaryKey(autoGenerate = true)
var id = 0
@ColumnInfo(name = "movie_name", defaultValue = "Harry Potter")
lateinit var name: String
@ColumnInfo(name = "actor_name", defaultValue = "Jack Daniel")
lateinit var actor: String
@ColumnInfo(name = "post_year", defaultValue = "1999")
var year = 1999
@ColumnInfo(name = "review_score", defaultValue = "8.0")
var score = 8.0
}
interface MovieDao {
@Insert
fun insert(vararg movies: Movie?): LongArray?
@Delete
fun delete(movie: Movie?): Int
@Update
fun update(vararg movies: Movie?): Int
@get:Query("SELECT * FROM movie")
val allMovies: LiveData<List<Movie?>?>
}
abstract class MovieDataBase : RoomDatabase() {
abstract fun movieDao(): MovieDao
companion object {
@Volatile
private var sInstance: MovieDataBase? = null
private const val DATA_BASE_NAME = "jetpack_movie.db"
@JvmStatic
fun getInstance(context: Context): MovieDataBase? {
if (sInstance == null) {
synchronized(MovieDataBase::class.java) {
if (sInstance == null) {
sInstance = createInstance(context)
}
}
}
return sInstance
}
private fun createInstance(context: Context): MovieDataBase {
return Room.databaseBuilder(context.applicationContext, MovieDataBase::class.java, DATA_BASE_NAME)
...
.build()
}
}
}
@Database 表示继承自RoomDatabase的抽象类,entities指定表的实现类列表,version指定了DB版本
必须提供获取DAO接口的抽象方法,比如上面定义的movieDao(),Room将通过这个方法实例化DAO接口
RoomDatabase实例的内存开销较大,建议使用单例模式管理
编译时将生成_Impl实现类,此处将生成MovieDataBase_Impl.java文件
private var movieViewModel: MovieViewModel? = null
private var binding: ActivityRoomDbBinding? = null
private var movieList: List<Movie?>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRoomDbBinding.inflate(layoutInflater)
setContentView(binding!!.root)
binding!!.lifecycleOwner = this
movieViewModel = ViewModelProvider(this).get(MovieViewModel::class.java)
movieViewModel?.getMovieList(this, { movieList: List<Movie?>? ->
if (movieList == null) return@getMovieList
this.movieList = movieList
binding?.setMovieList(movieList)
})
}
}
private val mediatorLiveData = MediatorLiveData<List<Movie?>?>()
private val db: MovieDataBase?
private val mContext: Context
init {
mContext = application
db = MovieDataBase.getInstance(mContext)
if (db != null) {
mediatorLiveData.addSource(db.movieDao().allMovies) { movieList ->
if (db.databaseCreated.value != null) {
mediatorLiveData.postValue(movieList)
}
}
};
}
fun getMovieList(owner: LifecycleOwner?, observer: Observer<List<Movie?>?>?) {
if (owner != null && observer != null)
mediatorLiveData.observe(owner, observer)
}
}
val databaseCreated = MutableLiveData<Boolean?>()
...
companion object {
...
private fun createInstance(context: Context): MovieDataBase {
return Room.databaseBuilder(context.applicationContext, ...)
...
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Executors.newFixedThreadPool(5).execute {
val dataBase = getInstance(context)
val ids = dataBase!!.movieDao().insert(*Utils.initData)
dataBase.databaseCreated.postValue(true)
}
}
...
})
.build()
}
}
}
OnConflictStrategy.REPLACE 冲突时替换为新记录
OnConflictStrategy.IGNORE 忽略冲突(不推荐)
OnConflictStrategy.ROLLBACK 已废弃,使用ABORT替代
OnConflictStrategy.FAIL 同上
fun insertWithOutId(movie: Movie?)
fun insert(movie: Movie?): Long?
fun insert(vararg movies: Movie?): LongArray?
查询指定字段
@get:Query(“SELECT id, movie_name, actor_name, post_year, review_score FROM movie”)
排序查询
@get:Query(“SELECT * FROM movie ORDER BY post_year DESC”) 比如查询最近发行的电影列表
匹配查询
@Query(“SELECT * FROM movie WHERE id = :id”)
多字段匹配查询
@Query(“SELECT * FROM movie WHERE movie_name LIKE :keyWord " + " OR actor_name LIKE :keyWord”) 比如查询名称和演员中匹配关键字的电影
模糊查询
@Query(“SELECT * FROM movie WHERE movie_name LIKE ‘%’ || :keyWord || ‘%’ " + " OR actor_name LIKE ‘%’ || :keyWord || ‘%’”) 比如查询名称和演员中包含关键字的电影
限制行数查询
@Query(“SELECT * FROM movie WHERE movie_name LIKE :keyWord LIMIT 3”) 比如查询名称匹配关键字的前三部电影
参数引用查询
@Query(“SELECT * FROM movie WHERE review_score >= :minScore”) 比如查询评分大于指定分数的电影
多参数查询
@Query(“SELECT * FROM movie WHERE post_year BETWEEN :minYear AND :maxYear”) 比如查询介于发行年份区间的电影
不定参数查询
@Query(“SELECT * FROM movie WHERE movie_name IN (:keyWords)”)
Cursor查询
@Query(“SELECT * FROM movie WHERE movie_name LIKE ‘%’ || :keyWord || ‘%’ LIMIT :limit”)
fun searchMoveCursorByLimit(keyWord: String?, limit: Int): Cursor?
注意:Cursor需要保证查询到的字段和取值一一对应,所以不推荐使用
响应式查询
demo采用的LiveData进行的观察式查询,还可以配合RxJava2,Kotlin的Flow进行响应式查询。
return Room.databaseBuilder(context.applicationContext, MovieDataBase::class.java, DATA_BASE_NAME)
.fallbackToDestructiveMigration()
.addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
// Init DB again after db removed.
Executors.newFixedThreadPool(5).execute {
val dataBase = getInstance(context)
val ids = dataBase!!.movieDao().insert(*Utils.initData)
dataBase.databaseCreated.postValue(true)
}
}
})
.build()
}
return Room.databaseBuilder(context.applicationContext, MovieDataBase::class.java, DATA_BASE_NAME)
// .fallbackToDestructiveMigration()
.addMigrations(object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE movie "
+ " ADD COLUMN review_score INTEGER NOT NULL DEFAULT 8.0")
}
})
...
})
.build()
}
public interface MovieDao {
@Transaction
default void insetNewAndDeleteOld(Movie newMovie, Movie oldMovie) {
insert(newMovie);
delete(oldMovie);
}
}
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
long[] _result = __insertionAdapterOfMovie.insertAndReturnIdsArray(movies);
__db.setTransactionSuccessful();
return _result;
} finally {
__db.endTransaction();
}
}
val database = db.getOpenHelper().getWritableDatabase();
val contentValues = ContentValues()
contentValues.put("movie_name", newMovie.getName())
contentValues.put("actor_name", newMovie.getActor())
contentValues.put("post_year", newMovie.getYear())
contentValues.put("review_score", newMovie.getScore())
database.insert("movie", SQLiteDatabase.CONFLICT_ABORT, contentValues)
database.delete("movie", "id = " + oldMovie.getId(), null)
})
RoomDatabase的实例建议采用单例模式管理
不要在UI线程执行DB操作,否则发生异常:Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
通过调用:
allowMainThreadQueries()可以回避,但不推荐
不要在Callback#onCreate()里同步执行insert等DB处理,否则将阻塞DB实例的初始化并发生异常:getDatabase called recursively。
@Entity注解类不要提供多个构造函数,使用@Ignore可以回避
Callback#onCreate()并非由RoomDatabase$Builder#build()触发,而是由具体的增删改查操作触发,切记
声明注解便能完成接口的定义,易上手
编译阶段将验证注解里声明的SQL语句,提高了开发效率
支持使用RxJava2,LiveData以及Flow进行异步查询
相较其他数据库框架SQL执行效率更高
https://github.com/ellisonchan/JetpackDemo