今天要聊的是在Room Database 底下一種名為資料表關聯(relationship)的實作

其實當初我會想做這個,主要是因為我純粹想了解要如何實作「多重資料表」...也就是如何在Room的框架下,在一個資料庫底下建立多個資料表的方式

然後循線一直查著查著...就開始研究到這個關鍵字-資料表關聯(relationship)了

那到底什麼是資料表關聯呢?我們來是想一個情境圖

首先,我們要製作一個「寵物登記系統

那,我所設計的系統如下

截圖 2021-08-28 下午11.08.26

 

如圖,可以看到我們的設定為一位主人會擁有多隻寵物

根據官方給出的定義,這是屬於one to many的一個架構

(網址在這: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542)

 

因此,今天的實作中,我們會把主人(Master)與寵物pet分成兩個資料表,如下圖

截圖 2021-08-28 下午11.47.37 2

 

那主人的資料表中,結構如下

截圖 2021-08-28 下午11.48.16

 

一共有五位主人,他們在申請成為主人時我設定系統會授與它一組UUID

然後是寵物的資料表

截圖 2021-08-28 下午11.48.22

 

寵物的資料表中,除了基本的ID之外,這張資料表亦會登記寵物名字與主人UUID

因此最後若是想調出該寵物相對應的主人的資料的話,便就是使用主人的UUID去搜尋主人並調出資料

okay,大致講解完今天要實作的東西了,那我們就來看實作了

來吧,上範例

&Github

->https://github.com/thumbb13555/RoomRelationshipsDemo

 


 

1. 載入庫及設置介面

 

先看一下這次的資料結構

截圖 2021-08-29 下午5.38.32

然後,載入這次所有需要的庫

請切到build.gradle(Module)

截圖 2021-08-29 下午5.52.12

 

在dependencies輸入以下

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    kapt 'androidx.room:room-compiler:2.4.0-alpha03'
    implementation 'com.facebook.stetho:stetho:1.5.1'
    implementation 'androidx.room:room-runtime:2.4.0-alpha03'
}

 

截圖 2021-08-29 下午5.58.34

Sync Now 完成編譯

介面也沒有什麼,直接給

activity_main.xml

截圖 2021-08-29 下午6.15.40

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView_Result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAlignment="center"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Spinner
        android:id="@+id/spinner_Select"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        app:layout_constraintBottom_toTopOf="@+id/textView_Result"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

至此,準備工作已完成40%~

 


 

2. 建立Room

 

關於Room資料庫建立在這裡有寫,可以先參考此處

->碼農日常-『Android studio』Room資料庫建立與操作(Kotlin)

 

所以我就不再多廢話,僅PO程式

截圖 2021-08-29 下午5.38.32

 

首先建立實體類

Master.kt

@Entity
data class Master(
    @PrimaryKey var id:String,
    var name:String,
    var phone:String
)

@Entity(
    foreignKeys = [ForeignKey(
        entity = Master::class,
        parentColumns = arrayOf("id"),
        childColumns = arrayOf("masterId"),
        onDelete = ForeignKey.CASCADE
    )]
)
data class Pet(
    @PrimaryKey (autoGenerate = true)
    var petId:Int,//一般數量1~10

    @ColumnInfo(name = "masterId")
    var masterId:String,//等於UUID
    var petName:String
)

這地方跟以往的資料表不同

之前的資料表通常就只有一個,所以一個實體類就可以搞定了

但是這裡不但有兩個,而且還要有關聯性

所以@Entity就有兩個,而且在第二個Entity下還得建立與主資料表之間的關連

 

截圖 2021-08-29 下午6.41.11

 

再來建立資料庫操作實體類

RoomDB.kt

@Database(entities = [Master::class, Pet::class], version = 1)
abstract class RoomDB : RoomDatabase() {
    abstract fun dao(): Dao?

    companion object {
        private var INSTANCE: RoomDB? = null

        fun getAppDatabase(context: Context): RoomDB? {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(
                    context.applicationContext, RoomDB::class.java, "MasterDB"
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return INSTANCE
        }
    }
}

特別注意一下粉底白字位置

粉底白字那邊就是載入多個資料表的方式,所以若是需求為僅是需要多個資料表的話,那只需這樣加入即可!

 

Dao.kt

@Dao
interface Dao {

    @Insert
    fun insertMaster(master: Master): Long

    @Insert
    fun insertPet(pet: Pet)

    @Query("Select * From master")
    fun getMasterList():List<Master>

    @Query("Select * From Master inner join Pet on Master.id = Pet.masterId where Master.id = :id")
    fun getMasterWithPets(id:String):List<MasterWithAllPets>?


}

這裡一樣沒有什麼,就是跟一般呼叫Room指令一樣

不過紅字部分會報錯,因為我們還沒寫關聯

因此接下來要寫關聯囉:D

 


 

3. 撰寫資料表關聯

 

雖然上段講得很偉大XD

不過其實根本就沒有什麼,簡單來說就是把在Master.kt中已定義好的entityColumn關聯起來

因此,程式如下

MasterWithAllPets.kt

class MasterWithAllPets (
    @Embedded var master: Master,
    @Relation (parentColumn = "id",entityColumn = "masterId") var pets:List<Pet>
        )

 

okay,到此為止已經把所有的準備工作完成了

而剛才報錯的地方也應該恢復了

接下來就是最後一步驟,資料庫操作囉

 


 

4. MainActivity 資料操作

 

最後都是資料庫操作的部分,一樣先全PO

MainActivity.kt

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName + "My"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        /**開啟Stetho資料庫監視*/
        Stetho.initializeWithDefaults(this)

        Thread {
            val dao = RoomDB.getAppDatabase(applicationContext)?.dao()
            //如果列表沒有任何數據,則新增
            if (dao!!.getMasterList().isEmpty()) {
                inputMasterWithPetData()
            } else {
                //載入主人與寵物資訊
                initMaster(dao)
            }
        }.start()
        /**選擇欲查詢之主人資訊*/
        spinner_Select.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
                Thread {
                    val dao = RoomDB.getAppDatabase(applicationContext)?.dao()
                    searchInfo(dao!!, p2)
                }.start()

            }
            override fun onNothingSelected(p0: AdapterView<*>?) {}
        }
    }

    /**載入主人資訊*/
    private fun initMaster(dao: Dao) {
        val allMaster = dao.getMasterList()
        val masterArray = ArrayList<String>()
        allMaster.forEach {
            Log.d(TAG, "所有的主人名單: ${it.name}, id: ${it.id}");
            masterArray.add(it.name)
        }
        val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, masterArray)
        runOnUiThread {
            spinner_Select.adapter = adapter
        }
    }

    /**搜尋該主人所飼養的寵物*/
    private fun searchInfo(dao: Dao, id: Int) {
        val allMaster = dao.getMasterList()
        val list = dao.getMasterWithPets(allMaster[id].id)
        val masterInfo = "主人${list!![0].master.name} 的寶貝們為: "
        Log.d(TAG, masterInfo);
        val string = StringBuffer(masterInfo + "\n")
        val pets = list[0].pets
        pets.forEach {
            val pet = "寵物名字: ${it.petName}"
            Log.d(TAG, pet)
            string.append(pet + "\n")
        }
        runOnUiThread {
            textView_Result.text = string.toString()
        }

    }

    /**手動增加測試資料*/
    private fun inputMasterWithPetData() {
        val dao = RoomDB.getAppDatabase(applicationContext)?.dao()
        val nameList = arrayListOf("Jack", "Noah", "Sam", "Tilly", "ShiYan")
        val phoneList =
            arrayListOf("091122334455", "0936589745", "0956842398", "038569741", "0988556412")
        val petList: Array<Array<String>> = arrayOf(
            arrayOf("小黑", "小白", "小黃"),
            arrayOf("小扁", "阿飛"),
            arrayOf("貓", "狗", "兔兔", "鳥"),
            arrayOf("老王"),
            arrayOf("大哈", "二哈"),
        )

        for (i in 0 until nameList.size) {
            val uuid = UUID.randomUUID().toString()
            val master = Master(uuid, nameList[i], phoneList[i])
            dao?.insertMaster(master)
            petList[i].forEach {
                val pet = Pet(0, uuid, it)
                dao?.insertPet(pet)
            }
        }
        //載入主人資訊
        initMaster(dao!!)
    }
}

 

分解一下重點

截圖 2021-08-29 下午7.18.01

 

我也有標記logcat輸出,也可以觀察一下

截圖 2021-08-29 下午7.21.35

ok,基本上所有程式就到這邊囉

Emmm我後面之所以沒再多解釋程式,是因為我覺得會搜尋到這篇的有很大的可能是本身就會寫Room的人

如果你搜到這篇但沒寫過Room,那再次建議你先看我這篇文章實作一次歐~

->碼農日常-『Android studio』Room資料庫建立與操作(Kotlin)

 


 

啊...超沒幹勁,超忙的啊這週...

我最近在寫一個APP,詳細在這裡

->【LikeCoin Grants Lite】LikeCoin 餘額即時顯示小工具(Android)

雖然忙,但是週更還是不能馬虎啊(笑)

這個APP我做出來是打算用來給大家查詢自己餘額的,也就是

碼農日常-『Android studio』AppWidgetProvider桌面小工具開發

所講到的APP

這個作品也預計會上架,敬請期待吧XD

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(6) 人氣()