【Kotlin/RoomDataBase】リレーションシップの設定方法!テーブル連携

この記事からわかること
- Android Studio/KotlinでRoomデータベースの使い方
- テーブル同士を連携する方法
- リレーションシップとは?
- 一対一や一対多などのリレーションの実装
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
RelationShipとは?
RelationShipとは異なるテーブル同士の関係を紐付けることです。これにより複数のテーブルがまるで1つのテーブルのように扱うことができ、レコード(行)に重複した情報を持たせる必要がなくなるため、スッキリとしたデータ構造で管理することができるようになります。例えばPerson
テーブルとCompany
テーブルがある場合、RelationShipを活用することで人の情報と会社の情報を切り離して定義することができるようになります。
以下のテーブルではPersonテーブルの「会社」で関係が紐づいています。
Personテーブル
id | 名前 | 年齢 | 会社 |
---|---|---|---|
1 | 吉田 真紘 | 27 | ABCデザイン |
2 | 長谷 慎二 | 34 | ABCデザイン |
3 | 川本 依 | 17 | XYZ制作 |
Companyテーブル
id | 名前 | 所在地 |
---|---|---|
1 | ABCデザイン | 東京都 |
2 | XYZ制作 | 神奈川県 |
RelationShipのタイプ
RelationShipには関係性の違いから3つのタイプに分かれています。
一対一リレーションシップ
各レコードが別のテーブル内のただ一つのレコードと関連付けられている状態。例えば、Personから見てCompanyは一対一リレーションシップの関係になる
一対多リレーションシップ
あるテーブルの一つのレコードが別のテーブル内の複数のレコードと関連付けられている状態。Companyには複数のPersonが紐づいているので一対多リレーションシップの関係になる
多対多リレーションシップ
両方のテーブルが複数のレコード同士を関連付けている状態。学生と科目のような関係で、1人の学生が複数の科目を取り、1つの科目が複数の学生によって選ばれる場合に多対多リレーションシップの関係になる
Roomで2つのテーブルにリレーションを設定する方法
Roomで2つのテーブルに一対多リレーションを設定するにはデータの構造(テーブル)部分であるエンティティクラスに記述する必要があります。例えば親となるCompany
クラスと子となるPerson
クラス同士にリレーションを持たせるように定義する場合は以下のようになります。
親:Companyエンティティクラス
@Entity(tableName = "company_table")
data class Company (
@PrimaryKey(autoGenerate = true) val id: Int,
val name: String,
)
子:Personエンティティクラス
@Entity(tableName = "person_table",
foreignKeys = arrayOf(
ForeignKey(
entity = Company::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("companyId"),
onDelete = ForeignKey.CASCADE
)
)
)
data class Person (
@PrimaryKey(autoGenerate = true) val id: Int,
val name: String,
val companyId: Int
)
リレーションを設定しているのは@Entity
アノテーションの引数foreignKeys
に設定しているForeignKey
クラスです。foreignKeys
には配列形式でリレーションを持たせたいクラスの情報をForeignKey
クラスで渡します。
ForeignKeyクラスとは?
ForeignKey
は日本語で「外部キー」という言葉通り、別のエンティティのキーに関する制約を設定できるクラスです。これにより外部の指定したキーが削除された際の動作など、異なるテーブル間でのデータに整合性を持たせることが可能になります。
ForeignKey
クラスの引数には以下の内容を渡します。
- entity:親のクラス名
- parentColumns:親クラスを参照するためのフィールド。親の主キー。
- childColumns:parentColumnsに設定したフィールドと同じ値を持つ、子のフィールド。
- onDelete:親が削除された時の子側の削除動作設定
この部分を設定することで2つのクラスにリレーションが設定されます。onDelete
にForeignKey.CASCADE
を指定することで親が削除された時にその親のidを子のcompanyIdプロパティに保持しているデータが自動で削除されるようになります。
@Entity(tableName = "person_table",
foreignKeys = arrayOf(
ForeignKey(
entity = Company::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("companyId"),
onDelete = ForeignKey.CASCADE
)
)
)
一括でデータを取得するには?
ここまでの設定で2つのテーブル間のリレーションを実装しましたが、これは親が削除された時に該当する子も削除されるということでした。なので厳密にはリレーション関係が結ばれているのはデータの削除時のみであり、取得する際には別々で取得したり、フィルタリングする必要があります。
一括で取得したい場合は以下の記事を参考にしてください。
実装サンプルコード
最後に動作確認ができるように必要な「Dao」、「Database」、「MainActivity」の3つのファイルを載せておきます。実用的なコードではないのでご了承ください。
Dao
@Dao
interface AppDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCompany(vararg company: Company)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPerson(vararg persons: Person)
@Query("SELECT * FROM company_table")
fun getAllCompany(): List<Company>
@Query("SELECT * FROM person_table")
fun getAllPerson(): List<Person>
@Delete
fun deleteCompany(vararg company: Company)
@Delete
fun deletePerson(vararg person: Person)
}
Database
@Database(entities = arrayOf(Company::class, Person::class), version = 1)
abstract class AppDatabase: RoomDatabase() {
abstract fun appDao(): AppDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db : AppDatabase
lateinit var dao : AppDao
var companys: List<Company>? = null
var persons: List<Person>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = AppDatabase.getDatabase(this)
dao = db.appDao()
var button1 = findViewById<Button>(R.id.button1)
var button2 = findViewById<Button>(R.id.button2)
var button3 = findViewById<Button>(R.id.button3)
var button4 = findViewById<Button>(R.id.button4)
var button5 = findViewById<Button>(R.id.button5)
var button6 = findViewById<Button>(R.id.button6)
button1.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
val company = Company(0, "会社1")
dao.insertCompany(company)
}
}
button2.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
companys = dao.getAllCompany()
Log.e("---Companys", companys.toString() )
}
}
button3.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
var company = companys!!.first()
dao.deleteCompany(company)
}
}
button4.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
var company = companys!!.first()
val person = Person(0, "社員1",company.id)
dao.insertPerson(person)
}
}
button5.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
persons = dao.getAllPerson()
Log.e("----persons", persons.toString() )
}
}
button6.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) {
var person = persons!!.first()
dao.deletePerson(person)
}
}
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。