Tworzenie odwołań do złożonych danych za pomocą funkcji Pokoje

Sala umożliwia konwertowanie między typami podstawowymi i pudełkowymi, ale nie zezwala na odwołania do obiektów między elementami. W tym dokumencie wyjaśniamy, jak korzystać z konwerterów typów i dlaczego sala nie obsługuje odwołań do obiektów.

Korzystanie z konwerterów typów

Czasami aplikacja chce przechowywać niestandardowy typ danych w jednej kolumnie bazy danych. Możesz obsługiwać typy niestandardowe, dodając przeliczniki typów, czyli metody, które informują pokój, jak konwertować typy niestandardowe na znane typy, które mogą pozostać w sali. Użytkownicy dokonujący konwersji typów rozpoznajesz za pomocą adnotacji @TypeConverter.

Załóżmy, że chcesz utrzymywać instancje Date w bazie danych sal. Sala nie wie, jak zachowywać obiekty Date, więc musisz zdefiniować konwertery typów:

Kotlin

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}

Java

public class Converters {
  @TypeConverter
  public static Date fromTimestamp(Long value) {
    return value == null ? null : new Date(value);
  }

  @TypeConverter
  public static Long dateToTimestamp(Date date) {
    return date == null ? null : date.getTime();
  }
}

W tym przykładzie zdefiniowano 2 metody konwerteraDateDateLongLong Sala wie, jak zachowywać obiekty Long, może więc używać konwerterów do utrwalania obiektów Date.

Następnie dodaj adnotację @TypeConverters do klasy AppDatabase, aby sala wiedziała o zdefiniowanej przez Ciebie klasie konwertera:

Kotlin

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

Java

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

Po zdefiniowaniu konwerterów tych typów możesz używać w elementach i DAO swojego typu niestandardowego tak samo jak w przypadku typów podstawowych:

Kotlin

@Entity
data class User(private val birthday: Date?)

@Dao
interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  fun findUsersBornOnDate(targetDate: Date): List<User>
}

Java

@Entity
public class User {
  private Date birthday;
}

@Dao
public interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  List<User> findUsersBornOnDate(Date targetDate);
}

W tym przykładzie sala może wszędzie używać konwertera zdefiniowanego typu, ponieważ dodał(a) adnotacje do AppDatabase o wartości @TypeConverters. Możesz też jednak ograniczyć konwersje typów do określonych encji lub DAO, dodając do klas @Entity lub @Dao adnotacje @TypeConverters.

Inicjowanie konwertera typu elementu sterującego

Zwykle Pokój tworzy za Ciebie instancje, które zrealizowały konwersję. Czasem może być jednak konieczne przekazanie dodatkowych zależności do klas konwertera typów. Oznacza to, że aplikacja musi bezpośrednio kontrolować inicjowanie konwerterów tego typu. W takim przypadku dodaj do klasy konwertera adnotację @ProvidedTypeConverter:

Kotlin

@ProvidedTypeConverter
class ExampleConverter {
  @TypeConverter
  fun StringToExample(string: String?): ExampleType? {
    ...
  }

  @TypeConverter
  fun ExampleToString(example: ExampleType?): String? {
    ...
  }
}

Java

@ProvidedTypeConverter
public class ExampleConverter {
  @TypeConverter
  public Example StringToExample(String string) {
    ...
  }

  @TypeConverter
  public String ExampleToString(Example example) {
    ...
  }
}

Następnie oprócz zadeklarowania klasy konwertera w @TypeConverters użyj metody RoomDatabase.Builder.addTypeConverter(), aby przekazać instancję klasy konwertera do konstruktora RoomDatabase:

Kotlin

val db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build()

Java

AppDatabase db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build();

Dlaczego pokój nie zezwala na odwołania do obiektów

Wniosek: sala nie zezwala na odwołania do obiektów między klasami encji. Musisz wyraźnie poprosić o dostęp do danych, których potrzebuje Twoja aplikacja.

Mapowanie relacji z bazy danych na odpowiedni model obiektowy jest typową praktyką i działa bardzo dobrze po stronie serwera. Nawet jeśli program wczytuje pola, do których uzyskują dostęp, serwer nadal działa dobrze.

Po stronie klienta ten typ leniwego ładowania nie jest jednak możliwy, ponieważ zwykle występuje w wątku interfejsu użytkownika, a wysyłanie zapytań o informacje na dysku w wątku interfejsu powoduje poważne problemy z wydajnością. Wątek interfejsu zazwyczaj zajmuje około 16 ms na obliczenie i rysowanie zaktualizowanego układu aktywności. Dlatego nawet jeśli zapytanie trwa tylko 5 ms, i tak może brakować czasu na narysowanie klatki w aplikacji, co powoduje zauważalne błędy wizualne. Ukończenie zapytania może zająć jeszcze więcej czasu, jeśli równolegle przebiega inna transakcja lub na urządzeniu uruchomione są inne zadania zajmujące dużo miejsca. Jeśli jednak nie używasz leniwego ładowania, aplikacja będzie pobierać więcej danych, niż jest to potrzebne, co powoduje problemy z wykorzystaniem pamięci.

W przypadku mapowania obiektów/relacji deweloperzy zwykle decydują się na to, by mogli zrobić to, co najlepiej pasuje do danego zastosowania. Deweloperzy zwykle decydują się na udostępnienie modelu w aplikacji i interfejsie. Rozwiązanie to nie jest jednak dobrze skalowane, ponieważ interfejs użytkownika zmienia się z biegiem czasu, co sprawia, że wspólny model stwarza problemy, których programiści nie mogą przewidywać i debugować.

Przeanalizujmy interfejs użytkownika, który wczytuje listę obiektów Book, a każda książka zawiera obiekt Author. Możesz początkowo zaprojektować zapytania z wykorzystaniem leniwego ładowania, aby wystąpienia funkcji Book pobierały autora. Podczas pierwszego pobierania pola author wysyła zapytanie do bazy danych. Po jakimś czasie okazuje się, że musisz też umieścić nazwisko autora w interfejsie aplikacji. Do tej nazwy można łatwo uzyskać dostęp, co pokazuje ten fragment kodu:

Kotlin

authorNameTextView.text = book.author.name

Java

authorNameTextView.setText(book.getAuthor().getName());

Ta pozornie nieszkodliwa zmiana powoduje jednak, że zapytanie o tabelę Author będzie wykonywane w wątku głównym.

Jeśli z wyprzedzeniem wyślesz zapytanie o informacje o autorze, trudno będzie zmienić sposób ładowania danych, gdy nie będą one już potrzebne. Jeśli na przykład w interfejsie aplikacji nie trzeba już wyświetlać informacji z atrybutem Author, aplikacja skutecznie wczytuje dane, których już nie wyświetla, co zużywa cenne miejsce w pamięci. Wydajność aplikacji obniża się jeszcze bardziej, jeśli klasa Author odwołuje się do innej tabeli, takiej jak Books.

Aby odwoływać się do wielu elementów jednocześnie za pomocą funkcji Room, utwórz funkcję POJO zawierającą poszczególne encje, a następnie wpisz zapytanie, które złącza odpowiednie tabele. Ten dobrze uporządkowany model w połączeniu z zaawansowanymi funkcjami weryfikacji zapytań pozwala aplikacji zużywać mniej zasobów podczas wczytywania danych, co poprawia jej wydajność i wygodę korzystania z niej.