Omet navegació

3.1 - Implementar relacions 1:N

ROOM és una llibreria senzilla, i fins i tot podríem dir que limitada, respecte a l'altra eina ORM que hem vist, Hibernate.

La implementació de les claus externes que expressen relacions entre les entitats del Model Entitat-Relació no és automàtica com en cas d'Hibernate. S'ha d'implementar a mà.

Anem a centrar-nos únicament en les relacions 1:N, per ser les més habituals. Una relació 1:N es traduïa al Model Relacional com una clau externa en l'entitat que participa amb cardinalitat N.

Anem a aplicar-lo a l'exemple CoffeeShops, que ja vam veure en el punt 2.2.1, però ampliant-lo per a tenir una relació 1:N: afegirem COMENTARIS al distints locals. D'aquesta manera, la Base de Dades ens quedarà (l'anomenarem CoffeeShops_Com.sqlite per a no confondre amb l'altra; la teniu penjada a l'Aula Virtual):

LOCAL (num: Integer, nom: Text, adreca: Text, punts: Integer, imatge: Blob)

COMENTARI (id_com: Integer, num_local: Integer, comentari: Text)

on num_local serà la clau externa que apunta a la clau principal de LOCAL.

Com havíem comentat en el punt anterior, la definició de la clau externa complica un poc la sintaxi. De moment no la definirem. Però hem de tenir clar que la definició de les anotacions ha d'anar d'acord amb la definicio de la Base de Dades. I per tant, si en la Base de Dades està definida la clau externa, també ho haurà d'estar en les anotacions. És per això que en aquest exemple no posarem la clau externa, ni en la Base de Dades ni en les anotacions.

Nota

Al final de tot, en el punt 3.3, de forma il·lustrativa s'explica com es defineix la clau externa amb les anotacions.

Per a poder definir la relació 1:N, és a dir, que un local té molts comentaris, i poder tenir en l'objecte de tipus Local la llista de comentaris, en ROOM ens veiem obligats a definir una nova classe, que arreplega un objecte de la classe que participa amb cardinalitat 1, i una llista d'objectes de la classe que participa amb cardinalitat, amb unes anotacions que definiran la relació. Així en el nostre exemple definirem un objecte de la classe Local i una llista d'objectes de la classe Comentari, i com hem dit, unes anotacions per a definir la relació. A aquesta nova classe l'anomenarem en l'exemple CoffeeWithComments, per a que siga il·lustrativa.

Entitats

Haurem de definir 3 entitats: la corresponent a Coffee, la de Comment, i com hem dit abans la que marca la relació, CoffeeWithComments.

En les dues primeres aprofitarem per adequar els noms dels camps als de les propietats que ja teníem, i substituiran les que ens venen de l'exemple de DI. Però les modificarem en l'apartat posterior. Ara només anem a il·lustrar el procés.

Coffee (equival a la taula LOCAL)

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName="LOCAL")
data class Coffee (
    @PrimaryKey val num: Int?,
    @ColumnInfo(name = "nom") val title: String?,
    @ColumnInfo(name = "adreca") val subtitle: String?,
    @ColumnInfo(name = "punts") val points: Int?,
    @ColumnInfo(name = "imatge") val image: ByteArray?
)

Observeu com no ha calgut posar @ColumnInfo en la primera propietat, perquè es diu igual que el nom del camp de la taula.

Comment (equival a la taula COMENTARIS)

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName="COMENTARIS")
data class Comment (
    @PrimaryKey @ColumnInfo(name = "id_com") val idCom: Int?,
    @ColumnInfo(name = "num_local") val numCoffee: Int?,
    @ColumnInfo(name = "comentari")val comm: String?
)

Ara sí que hem definit totes les vegades @ColumnInfo perquè fins i tot en el primer camp estem canviant el nom

CoffeeWithComments

Aquesta és la novetat, que crearem en un nou fitxer CoffeWithComments:

import androidx.room.Embedded
import androidx.room.Relation
import com.example.coffeeShops_fragments_room.Coffee
import com.example.coffeeShops_fragments_room.Comment
import java.io.Serializable

data class CoffeeWithComments (
    @Embedded val coffee: Coffee,
    @Relation(
        parentColumn = "num",
        entityColumn = "num_local"
    )
    val coms: List<Comment>
): Serializable

L'hem definida com a Serializable per un motiu que no té res a veure amb la definició. Ja s'explicarà en el seu moment.

Hem definit:

  • Un objecte de la classe Coffee. Posem l'anotació @Embedded per a indicar que és un objecte que a la seua vegada té anotacions.
  • Amb l'anotació @Relation definim la relació, especificant el nom del camp en la taula principal (LOCAL) amb parentColumn i el de la relacionada (COMENTARI) amb entityColumn. Observeu que hem de posar els noms dels camps de les taules, no les propietats de les classes.
  • Per últim un List d'objectes de la classe Comments

DAO

En aquesta classe ens definim els accessos que voldrem fer a la Base de Dades, tant consultes com actualitzacions. Per a que s'entenga bé, posarem dos consultes:

  • Una que contindrà únicament els Locals. A partir d'ells no podem accedir als comentaris
  • Una altra que contindrà LocalsAmbComentaris, i per tant podrem accedir als locals i als comentaris

Observeu que curiosament la consulta és idèntica, però canvia la llista d'elements tornats: en el primer cas és List<Coffee> i en el segon cas és List<CoffeeWithComments>. Ho podem guardar al fitxer CoffeeShopsDao.kt

import androidx.room.Dao
import androidx.room.Query
import com.example.coffeeShops_fragments_room.Coffee

@Dao
interface CoffeeShopsDao {
    @Query("SELECT * FROM LOCAL")
    fun getCoffeesWithComments(): List<CoffeeWithComments>

    @Query("SELECT * FROM LOCAL")
    fun getCoffees(): List<Coffee>
}

RoomDatabase

Només hem d'observar que hem de posar les 2 entitats de les quals agafem informació: Local i Comentaris. Justament per si hi ha més d'una taula de la qual agafar informació entre parèntesis s'especifica un Array. Ho podem guardar en un fitxer anomenat CoffeeShopsDatabase.kt

import androidx.room.Database
import androidx.room.RoomDatabase
import com.example.coffeeShops_fragments_room.Coffee
import com.example.coffeeShops_fragments_room.Comment

@Database(entities = arrayOf(Coffee::class, Comment::class), version = 1)
abstract class CoffeeShopsDatabase : RoomDatabase() {
    abstract fun coffeeshopsDao(): CoffeeShopsDao
}