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 farsi riferimento a vicenda, Room lo vieta esplicitamente. Per scoprire il ragionamento tecnico alla base di questa decisione, consulta Comprendere perché Room non consente riferimenti a oggetti.

Tipi di relazioni

Room supporta i seguenti tipi di relazione:

  • 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 richiede una tabella di unione.
  • Relazioni nidificate (utilizzando oggetti incorporati): rappresenta una relazione in cui un'entità contiene un'altra entità come campo e questa entità nidificata può contenere altre entità. In questo modo 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
  • Un metodo di query relazionale con un tipo restituito multimappa.

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

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 al tipo restituito multimap richiede che le query SQL facciano più lavoro, mentre l'approccio alla classe di dati intermedia richiede che il codice faccia 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. I metodi di query possono quindi restituire istanze di questa classe di dati da utilizzare nella tua app.

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

Kotlin

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

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

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();
}

public class UserBook {
    public String userName;
    public String bookName;
}

Utilizzare l'approccio dei tipi restituiti dalla multimappa

Nell'approccio con tipo restituito multimappa, non devi definire classi di dati aggiuntive. Definisci invece un tipo restituito multimap per il tuo metodo in base alla struttura della mappa che ti interessa e definisci la relazione tra le entità direttamente nella query SQL.

Ad esempio, il seguente metodo di query restituisce un mapping delle istanze User e Book per rappresentare gli utenti della biblioteca con libri specifici presi in prestito:

Kotlin

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

Java

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

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 diversi campi. In queste situazioni, puoi utilizzare l'annotazione @Embedded per rappresentare un oggetto che vuoi scomporre nei relativi campi secondari all'interno di una tabella. Puoi quindi interrogare i campi incorporati proprio come fai per le altre singole colonne.

Ad esempio, la classe User può includere un campo di tipo Address che rappresenta una composizione di campi denominati street, city, state e postCode. Per archiviare le colonne composte separatamente nella tabella, includi un campo Address. Dovrebbe essere visualizzato nella classe User annotata con @Embedded. Il seguente snippet di codice mostra questo concetto:

Kotlin

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?
)

Java

public class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code") public int postCode;
}

@Entity
public class User {
    @PrimaryKey public int id;

    public String firstName;

    @Embedded public 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ù campi incorporati dello stesso tipo, puoi mantenere ogni colonna univoca impostando la proprietà prefix. Room aggiunge quindi il valore fornito all'inizio di ogni nome di colonna nell'oggetto incorporato.

Risorse aggiuntive

Per scoprire di più sulla definizione delle relazioni tra le entità in Room, consulta le seguenti risorse aggiuntive.

Video

Blog