Room proporciona funciones para convertir entre tipos primitivos y encuadrados, pero no admite referencias de objetos entre entidades. En este documento, se explica cómo usar convertidores de tipos y por qué Room no admite referencias de objetos.
Cómo usar convertidores de tipo
A veces, tu app necesita usar un tipo de datos personalizado cuyo valor deseas almacenar en una sola columna de base de datos. Para agregar este tipo de compatibilidad con los tipos personalizados, debes proporcionar un objeto TypeConverter
, que convierte una clase personalizada en un tipo conocido para que Room pueda conservarlo y viceversa.
Por ejemplo, si queremos conservar instancias de Date
, podemos escribir el siguiente TypeConverter
para almacenar la marca de tiempo de Unix equivalente en la base de datos:
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(); } }
En el ejemplo anterior, se definen 2 funciones: una que convierte un objeto Date
en uno Long
, y otra que realiza la conversión inversa (de Long
a Date
). Como Room ya sabe cómo conservar objetos Long
, puede usar este convertidor para conservar los valores del tipo Date
.
A continuación, debes agregar la anotación @TypeConverters
a la clase AppDatabase
para que Room pueda usar el convertidor que definiste en cada entidad y DAO en esa clase AppDatabase
:
AppDatabase
Kotlin
@Database(entities = arrayOf(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(); }
Con estos convertidores, puedes usar tus tipos personalizados en otras consultas, tal como lo harías con los tipos primitivos, como se muestra en el siguiente fragmento de código:
User
Kotlin
@Entity data class User(private val birthday: Date?)
Java
@Entity public class User { private Date birthday; }
UserDao
Kotlin
@Dao interface UserDao { @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to") fun findUsersBornBetweenDates(from: Date, to: Date): List<User> }
Java
@Dao public interface UserDao { @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to") List<User> findUsersBornBetweenDates(Date from, Date to); }
También puedes limitar los elementos @TypeConverters
a diferentes ámbitos, incluidas entidades individuales, DAO y métodos DAO. Para obtener información detallada, consulta la documentación de referencia de la anotación @TypeConverters
.
Por qué Room no permite referencias a objetos
Conclusión clave: Room no permite referencias de objetos entre clases de entidades. En su lugar, debes solicitar explícitamente los datos que necesita tu app.
La asignación de relaciones de una base de datos al modelo de objetos correspondiente es una práctica común y funciona muy bien en el servidor. Incluso cuando el programa carga los campos a medida que se accede a ellos, el servidor sigue funcionando correctamente.
Sin embargo, en el cliente, este tipo de carga diferida no es factible porque, generalmente, ocurre en el subproceso de IU y la consulta de información en el subproceso de IU del disco crea problemas de rendimiento significativos. El subproceso de IU suele tener alrededor de 16 ms para calcular y dibujar el diseño actualizado de una actividad, por lo que, incluso si una consulta solo lleva 5 ms, es probable que tu app se quede sin tiempo para dibujar el marco de trabajo, lo que provocaría fallas visuales notables. La consulta podría tardar más tiempo en completarse si hay una transacción independiente ejecutándose en paralelo o si el dispositivo ejecuta otras tareas que requieren mucha memoria. Sin embargo, si no usas la carga diferida, tu app obtiene más datos de los que necesita, lo que crea problemas de consumo de memoria.
Las asignaciones relacionales de objetos generalmente dejan esta decisión a los desarrolladores con el objetivo de que puedan hacer lo que sea mejor para los casos prácticos de sus apps. Los desarrolladores a menudo deciden compartir el modelo entre su app y la IU. Sin embargo, esta solución no se ajusta bien porque, a medida que la interfaz de usuario cambia con el tiempo, el modelo compartido crea problemas que son difíciles de anticipar y depurar para los desarrolladores.
Por ejemplo, considera una IU que carga una lista de objetos Book
, cada uno con un objeto Author
. Inicialmente, puedes diseñar tus consultas de modo que utilicen la carga diferida para que las instancias de Book
obtenga el autor. La primera obtención del campo author
consulta la base de datos. Un tiempo después, notas que también debes mostrar el nombre del autor en la IU de tu app. Puedes acceder a este nombre con facilidad, como se muestra en el siguiente fragmento de código:
Kotlin
authorNameTextView.text = book.author.name
Java
authorNameTextView.setText(book.getAuthor().getName());
Sin embargo, este cambio aparentemente insignificante hace que se consulte la tabla Author
en el subproceso principal.
Si consultas la información del autor con anticipación, se hace difícil cambiar cómo se cargan esos datos si ya no los necesitas. Por ejemplo, si la IU de tu app ya no necesita mostrar información de Author
, habrá cargado esos datos igualmente sin mostrarlos, provocando una pérdida de valioso espacio de memoria. La eficacia de tu app se degrada aún más si la clase Author
hace referencia a otra tabla, como Books
.
Para hacer referencia a varias entidades con Room simultáneamente, debes crear un POJO que contenga cada entidad y, luego, escribir una consulta que una las tablas correspondientes. Este modelo bien estructurado, combinado con las sólidas capacidades de validación de consultas de Room, permite que tu app consuma menos recursos durante la carga de datos, mejorando el rendimiento de tu app y la experiencia del usuario.