Room增删改查,真香!

1,463 阅读3分钟

Android官方推出了一个ORM框架,并将它加入了Jetpack当中,这就是Room 。本文实现的Demo使用到了room+ViewModel+liveData+viewBinding

本文实现的效果图

ezgif-2-9765ffe3f2ce.gif

Room整体架构

它主要由Entity、Dao和Database这3部分组 成,每个部分都有明确的职责,详细说明如下。

Entity

用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并 且表中的列是根据实体类中的字段自动生成的。

Dao

Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编 程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。

Database

Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编 程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。

实现代码

image.png

添加依赖

本文用到的添加依赖,需要在app/bulid.gradle中添加

plugins {
    id 'kotlin-kapt'  //kotlin-kapt插件 room编译注解库  kapt只能在kotlin中使用
}

  //ViewBinding  
  buildFeatures {
        viewBinding true
  }
  
dependencies {
   //lifecycle
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    //侧滑删除
    implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
    //BaseRecyclerViewAdapterHelper能够简化适配器代码
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
    //room
    implementation 'androidx.room:room-runtime:2.1.0'
    kapt 'androidx.room:room-compiler:2.1.0'
    }

Book 实体类

这里在Book类名上使用@Entity注解将它声明为实体类, 并设置tab名 ,然后在类中添加id字段,@PrimaryKey设置为主键,并通过autoGenerate =true设置为自增,另外还设置了name,price字段

@Entity(tableName = "tb_book")
 class Book(var name:String,var price:Int) {
    @PrimaryKey(autoGenerate = true)
    var id:Long=0
}

BookDao

它属于room中最关键的方法,所有访问数据库都在这里封装,就是使用注解,编写增删改查的sql语句

@Dao
interface BookDao {
    /**
     * 添加
     */
    @Insert
    fun insertBook(book: Book):Long

    /**
     * 根据id修改名称
     */
    @Query("UPDATE tb_book SET name=:name WHERE id=:id")
    fun updateBookPrice(name:String,id:Long)

    /**
     * 查询所有
     */
    @Query("SELECT * FROM tb_book")
    fun loadAllBook():LiveData<List<Book>>


    /**
     * 根据id删除book
     */
    @Query("DELETE FROM tb_book WHERE id=:id")
    fun deleteBookById(id:Long):Int
}

AppDatabase

这部分内容的写法是非常固定的,只需要定义好 3个部分的内容:数据库的版本号、包含哪些实体类,以及提供Dao层的访问实例。

@Database(version = 1,entities = [Book::class],exportSchema = false)
abstract class AppDatabase : RoomDatabase(){
    abstract fun bookDao(): BookDao
    companion object{
        private var instance:AppDatabase?=null
        @Synchronized
        fun getDatabase(context: Context):AppDatabase{
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext,
                AppDatabase::class.java,"app_db")
                .build().apply {
                    instance=this
                }
        }
    }
}

Repository

object Repository {

    val bookDao:BookDao=AppDatabase.getDatabase(MyApp.context).bookDao()

    //查询所有
    fun loadAllBook():LiveData<List<Book>>{
        return bookDao.loadAllBook()
    }

    //修改图书价格
    fun updateBookPrice(name:String,id:Long){
        bookDao.updateBookPrice(name,id)
    }

    //添加图书
    fun insertBook(book: Book):Long{
        return bookDao.insertBook(book)
    }

    //根据id删除
    fun deleteBookById(id:Long):Int{
        return bookDao.deleteBookById(id)
    }

}

MainViewModel

class MainViewModel :ViewModel() {

    //查询所有的book
    fun getAll():LiveData<List<Book>>{
        return Repository.loadAllBook()
    }
}

MainActivity

这里要注意数据库操作是属于耗时的操作,Room默认是不允许放在主线程中进行数据库操作,所以我们把对数据库增删改的操作放到了子线程中。

class MainActivity : AppCompatActivity() {
    private lateinit var binding:ActivityMainBinding
    private val list=ArrayList<Book>()
    private lateinit var adapter: BookAdapter
    private val mainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding= ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        //初始化适配器
        adapter=BookAdapter(R.layout.item_book,list)
        binding.rvBook.adapter=adapter


        //viewModel+liveData监听数据变化---数据库数据一旦发生变化 这里就会收到通知
        mainViewModel.getAll().observe(this, Observer {
            list.clear()
            Log.d(TAG, "onCreate: ${it.size}")
            list.addAll(it)
            adapter.notifyDataSetChanged()
        })
        //注册点击事件
        adapter.addChildClickViewIds(R.id.btnDelete,R.id.btnUpdate)

        //item子控件点击事件
        adapter.setOnItemChildClickListener(object :OnItemChildClickListener{
            override fun onItemChildClick(
                adapter: BaseQuickAdapter<*, *>,
                view: View,
                position: Int
            ) {
                if(view.id==R.id.btnDelete){//删除按钮
                    thread {
                        Repository.deleteBookById(list[position].id)
                    }
                }else if(view.id==R.id.btnUpdate){//修改按钮
                    thread {
                        Repository.updateBookPrice("我被修改了!!",list[position].id)
                    }
                }

            }
        })

        //添加
        binding.btnAdd.setOnClickListener {
            val name = binding.edtBook.text.toString().trim()
            val price = binding.edtPrice.text.toString()
            if(name==""||price==""){
                Toast.makeText(this, "请填写完整", Toast.LENGTH_SHORT).show()
            }else{
                val book=Book(name,price.toInt())
                thread {
                    val id=Repository.insertBook(book)
                    Log.d(TAG, "onCreate: ${id}")
                }
            }

        }
    }

    companion object {
        private const val TAG = "MainActivity"
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

   <LinearLayout
       android:id="@+id/ll_edt"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       >
       <EditText
           android:id="@+id/edt_book"
           android:layout_width="wrap_content"
           android:layout_height="50dp"
           android:layout_weight="1"
           android:hint="输入名称"/>
       <View
           android:layout_width="1dp"
           android:layout_height="match_parent"
           android:layout_marginLeft="20dp"
           android:layout_marginRight="20dp"
           android:background="#000000"/>
       <EditText
           android:id="@+id/edt_price"
           android:layout_width="wrap_content"
           android:layout_height="50dp"
           android:layout_weight="1"
           android:hint="输入价格"/>
       <Button
           android:id="@+id/btn_add"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="添加"/>
   </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_book"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        />

</LinearLayout>

BookAdapter

class BookAdapter(layoutId:Int,list:ArrayList<Book>):BaseQuickAdapter<Book,BaseViewHolder>(layoutId,list){
    override fun convert(holder: BaseViewHolder, item: Book) {
        holder.setText(R.id.tv_name,item.name.toString())
        holder.setText(R.id.tv_price,item.price.toString())
    }

}

item_book.xml

这里的item父布局我们用到了SwipeMenuLayout,这里是使用一个支持侧滑的依赖库实现,前面添加依赖的操作已作说明。

<?xml version="1.0" encoding="utf-8"?>
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:layout_marginBottom="5dp"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="59dp">
            <TextView
                android:id="@+id/tv_name"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="活着"/>
            <TextView
                android:id="@+id/tv_price"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="12"/>
        </LinearLayout>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#d0d0d0"/>
    </LinearLayout>
    <Button
        android:id="@+id/btnUpdate"
        android:layout_width="60dp"
        android:layout_height="match_parent"
        android:background="#BEBEBE"
        android:text="修改"
        android:textColor="@android:color/white"/>
    <Button
        android:id="@+id/btnDelete"
        android:layout_width="60dp"
        android:layout_height="match_parent"
        android:background="#ff0000"
        android:text="删除"
        android:textColor="@android:color/white"/>


</com.mcxtzhang.swipemenulib.SwipeMenuLayout>

MyApp

这里定义了全局Context。不要忘记在AndroidManifest.xml中注册添加MyApp哟

class MyApp :Application(){
    companion object{
        lateinit var context: Context
    }
    override fun onCreate() {
        super.onCreate()
        context=applicationContext
    }
}

注:部分内容参考自 郭神《第一行代码Android第三版》