Scegliere i tipi di relazione tra gli oggetti

Poiché SQLite è un database relazionale, puoi definire le relazioni tra le entità. Tuttavia, mentre la maggior parte delle librerie di mapping relazionale degli oggetti consente agli oggetti entità di fare riferimento l'uno all'altro, Room lo vieta esplicitamente. Per scoprire il ragionamento tecnico alla base di questa decisione, consulta la pagina Comprendere perché Room non consente i riferimenti agli oggetti.

Tipi di relazioni

Room supporta i seguenti tipi di relazioni:

  • Uno-a-uno: rappresenta una relazione in cui una singola entità è correlata a un'altra singola entità.
  • Uno-a-molti: rappresenta una relazione in cui una singola entità può essere correlata a più entità di un altro tipo.
  • Molti-a-molti: rappresenta una relazione in cui più entità di un tipo possono essere correlate a più entità di un altro tipo. In genere è necessaria una tabella di collegamento.
  • Relazioni nidificate che utilizzano oggetti incorporati: rappresenta una relazione in cui un'entità contiene un'altra entità come proprietà e questa entità nidificata può contenere ulteriormente altre entità. Viene utilizzata l'annotazione @Embedded.

Scegliere tra due approcci

In Room, esistono due modi per definire ed eseguire query su una relazione tra entità. Puoi utilizzare:

  • Una classe di dati intermedia con oggetti incorporati oppure
  • Una funzione di query relazionale con un tipo restituito multimap.

Se non hai un motivo specifico per utilizzare le classi di dati intermedie, ti consigliamo di utilizzare l'approccio del tipo restituito multimap. Per saperne di più su questo approccio, consulta la pagina Restituire una multimap.

L'approccio della classe di dati intermedia ti consente di evitare di scrivere query SQL complesse, ma può anche comportare una maggiore complessità del codice perché richiede classi di dati aggiuntive. In breve, l'approccio del tipo restituito multimap richiede che le query SQL svolgano più lavoro, mentre l'approccio della classe di dati intermedia richiede che il codice svolga più lavoro.

Utilizzare l'approccio della classe di dati intermedia

Nell'approccio della classe di dati intermedia, definisci una classe di dati che modella la relazione tra le entità Room. Questa classe di dati contiene gli accoppiamenti tra le istanze di un'entità e le istanze di un'altra entità come oggetti incorporati. Le funzioni di query possono quindi restituire istanze di questa classe di dati da utilizzare nell'app.

Ad esempio, puoi definire una classe di dati UserBook per rappresentare gli utenti della libreria con libri specifici in prestito e definire una funzione di query per recuperare un elenco di istanze UserBook dal database:

@Dao
interface UserBookDao {
    @Query(
        """
        SELECT user.name AS userName, book.name AS bookName
        FROM user JOIN book ON user.id = book.user_id
        """
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>
}

data class UserBook(val userName: String, val bookName: String)

Utilizzare l'approccio del tipo restituito multimap

Nell'approccio del tipo restituito multimap, non è necessario definire classi di dati aggiuntive. Definisci invece un tipo restituito multimap per la funzione in base alla struttura della mappa che vuoi e definisci la relazione tra le entità direttamente nella query SQL.

Ad esempio, la seguente funzione di query restituisce un mapping delle istanze User e Book per rappresentare gli utenti della libreria con libri specifici in prestito:

@Query(
    """
    SELECT *
    FROM user JOIN book ON user.id = book.user_id
    """
)
suspend fun loadUserAndBookNames(): Map<User, List<Book>>

Con i tipi restituiti multimap, puoi anche eseguire query sulle relazioni uno-a-uno che non coinvolgono un'altra entità. La seguente funzione di query restituisce un mapping di User e il numero di libri che hanno in prestito utilizzando l' @MapColumn annotazione:

@Query(
    """
    SELECT user.*, COUNT(book.id) AS book_count
    FROM user LEFT JOIN book ON user.id = book.user_id
    GROUP BY user.id
    """
)
suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>

Creare oggetti incorporati

A volte, vuoi esprimere un'entità o un oggetto dati come un insieme coeso nella logica del database, anche se l'oggetto contiene diverse proprietà. In questi casi, utilizza l'annotazione @Embedded per scomporre un oggetto nelle relative sotto proprietà all'interno di una tabella. Puoi quindi eseguire query sulle proprietà incorporate come fai per le altre colonne.

Ad esempio, la classe User può includere una proprietà Address che rappresenta una composizione delle proprietà street, city, state e postCode. Per archiviare le colonne composte separatamente nella tabella, annota la proprietà Address nella classe User con @Embedded. Il seguente snippet di codice mostra questa configurazione:

data class Address(
    val street: String?,
    val state: String?,
    val city: String?,
    @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String,
    @Embedded val address: Address?
)

La tabella che rappresenta un oggetto User contiene quindi colonne con i seguenti nomi: id, firstName, street, state, city e post_code.

Se un'entità ha più proprietà incorporate dello stesso tipo, puoi mantenere univoca ogni colonna impostando la prefix proprietà. Room aggiunge quindi il valore fornito all'inizio di ogni nome di colonna nell'oggetto incorporato.