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

この記事からわかること

  • Android Studio/KotlinRoomデータベース使い方
  • テーブル同士連携する方法
  • リレーションシップとは?
  • 一対一一対多などのリレーション実装

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

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は日本語で「外部キー」という言葉通り、別のエンティティのキーに関する制約を設定できるクラスです。これにより外部の指定したキーが削除された際の動作など、異なるテーブル間でのデータに整合性を持たせることが可能になります。

ForeignKeyクラスの引数には以下の内容を渡します。

この部分を設定することで2つのクラスにリレーションが設定されます。onDeleteForeignKey.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)
            }
        }
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index