Room es una biblioteca para trabajar con SQLite en Android, parte de Jetpack. Proporciona una capa de abstracción sobre SQLite puro, automatizando la creación de tablas, la ejecución de consultas y la conversión de datos en objetos Kotlin y Java. Según Android Developers, Room compila las consultas SQL en tiempo de compilación, verificando la corrección de la sintaxis y las relaciones entre Entity y las tablas.
Puntos clave
Room es una biblioteca de persistencia de Android Jetpack que proporciona mapeo objeto-relacional para SQLite. Room resuelve tres problemas principales de SQLite puro: escribir grandes cantidades de código boilerplate para crear tablas, la falta de verificación de consultas SQL en tiempo de compilación y la conversión manual de Cursor a objetos.
La biblioteca utiliza un procesador de anotaciones (kapt o KSP) que genera la implementación de las clases abstractas RoomDatabase y DAO en tiempo de compilación. Esto garantiza que los errores de sintaxis en SQL y las discrepancias de tipos se descubran antes de ejecutar la aplicación, en lugar de en tiempo de ejecución después de publicar en Google Play.
Según Google I/O 2023, Room se utiliza en el 68% de las aplicaciones Android que trabajan con datos locales. Es el estándar para el almacenamiento de datos en el dispositivo, recomendado por Google para todos los proyectos nuevos, en lugar de los obsoletos SQLiteOpenHelper y ContentProvider.
Integre Room en proyectos que requieran almacenamiento en caché local de datos del servidor, modo sin conexión o almacenamiento de datos estructurados de usuario con la capacidad de ejecutar consultas SQL complejas.
Room es parte de Android Jetpack y está oficialmente recomendado por Google para todos los proyectos nuevos que trabajan con datos locales. A diferencia de Realm u ObjectBox, Room utiliza SQLite nativo, lo que garantiza la compatibilidad con cualquier herramienta de base de datos de terceros, desde DB Browser hasta DataGrip. Los desarrolladores pueden abrir el archivo .db de la aplicación y ejecutar consultas SQL directamente, lo que simplifica la depuración y el análisis de datos durante el desarrollo.
Entity es una clase de datos anotada con @Entity que Room transforma en una tabla de base de datos. Cada campo de la clase se convierte en una columna de la tabla, y cada instancia en una fila. Room utiliza reflexión para acceder a los campos, por lo que se requiere la anotación @PrimaryKey para un identificador obligatorio.
La anotación @Entity indica a Room que la clase es una tabla. El parámetro tableName establece el nombre de la tabla si difiere del nombre de la clase. @PrimaryKey define la clave principal con soporte de autogeneración mediante autoGenerate = true.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfo especifica el nombre de la columna en la tabla si difiere del nombre del campo en Kotlin. @Ignore excluye un campo de la tabla — no se guardará en la base de datos. @ForeignKey describe claves foráneas para relaciones entre tablas con operaciones en cascada al eliminar o actualizar.
Room admite objetos anidados mediante la anotación @Embedded. Los campos de la clase anidada se expanden en columnas de la tabla principal con un prefijo para evitar conflictos de nombres. Por ejemplo, una clase Address con campos city y street incrustada en User creará las columnas address_city y address_street en la tabla users, eliminando la necesidad de tablas separadas para objetos de valor simples.
Room solo admite tipos primitivos y sus envoltorios. Para almacenar listas, Date o tipos personalizados, use @TypeConverter — métodos estáticos para convertir entre un tipo personalizado y un primitivo de SQLite, por ejemplo, entre List y una cadena JSON.
DAO (Data Access Object) es una interfaz o clase abstracta anotada con @Dao que contiene métodos para el acceso a datos. Cada método se anota con una operación SQL: @Insert, @Update, @Delete o @Query con una consulta SQL explícita.
La anotación @Query toma una cadena SQL que Room verifica en tiempo de compilación para la corrección de la sintaxis y la coincidencia de nombres de columna con los campos de Entity. Room admite consultas parametrizadas mediante la sintaxis :paramName.
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insert admite estrategias OnConflictStrategy para manejar conflictos al insertar registros duplicados. Flow como tipo de retorno proporciona actualizaciones reactivas de la interfaz de usuario en cada cambio de datos en la tabla — la suscripción se reinicia automáticamente en cualquier INSERT, UPDATE o DELETE.
La anotación @Transaction garantiza la ejecución atómica de múltiples operaciones dentro de un único bloque transaccional. Room bloquea la base de datos durante la ejecución, evitando condiciones de carrera durante el acceso concurrente desde múltiples hilos.
RoomDatabase es una clase abstracta que combina Entity y DAO en un único punto de acceso a la base de datos. Se crea mediante Room.databaseBuilder con la versión del esquema y una lista de clases Entity. La instancia de la base de datos debe crearse como un singleton utilizando un delegado lazy para evitar múltiples conexiones.
Una migración en Room es una clase Migration que describe un script SQL para la transición de una versión antigua del esquema a una nueva. Si no se proporciona una migración cuando cambia el esquema, Room lanza una IllegalStateException. Esto protege contra la pérdida accidental de datos del usuario al actualizar la aplicación.
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
Para el desarrollo, puede usar fallbackToDestructiveMigration, que elimina la base de datos antigua y crea una nueva en caso de discrepancia de versiones. Este modo está destinado solo para depuración — las versiones de producción deben incluir migraciones adecuadas.
Para probar la base de datos, Room proporciona una clase especial Room.inMemoryTestBuilder que crea una base de datos en memoria sin guardar en disco. Después de cada prueba, la base de datos se destruye automáticamente, lo que garantiza un aislamiento completo de los escenarios de prueba. Combinado con la biblioteca android-arch-core-testing, los desarrolladores pueden gestionar el ciclo de vida de la base de datos y verificar la corrección de las migraciones sin limpiar el estado manualmente.
El rendimiento de Room depende directamente de la estructura de las consultas y los índices. Para analizar consultas lentas, Room proporciona el indicador enableQueryCallback, que registra todas las consultas SQL con el tiempo de ejecución. Los desarrolladores pueden usar este registro para encontrar consultas que se ejecutan durante más de 100 milisegundos y optimizarlas agregando índices compuestos mediante la anotación @Index en @Entity o reescribiendo subconsultas como operaciones JOIN directas usando @Relation.
Room también admite el cifrado de bases de datos mediante SQLCipher. Agregar la biblioteca net.zetetic:android-database-sqlcipher y usar SupportFactory en lugar del estándar proporciona un cifrado transparente de todos los datos en disco sin cambiar las consultas DAO ni la estructura Entity. Esto es necesario para aplicaciones que manejan datos personales de usuarios y cumple con el GDPR y la Ley Federal Rusa 152-FZ sobre protección de datos personales. La contraseña de cifrado se puede almacenar en Android Keystore para protegerla contra la extracción mediante herramientas en dispositivos rooteados.
Room es compatible de forma nativa con Kotlin Coroutines a partir de la versión 2.1. Los métodos DAO pueden ser funciones suspend que ejecutan consultas en el hilo de fondo sin bloquear el hilo principal. Room gestiona automáticamente los despachadores, utilizando Dispatchers.IO para consultas de lectura y escritura.
Para consultas reactivas, Room devuelve un Flow — un flujo de datos frío que emite un nuevo valor en cada cambio en la tabla afectada. ViewModel se suscribe a Flow mediante stateIn o collect, proporcionando actualizaciones automáticas de la interfaz de usuario sin notificar manualmente al adaptador.
Room también es compatible con Paging 3 a través de una implementación especial de PagingSource que carga datos página por página desde SQLite. Esto es eficiente para listas grandes con miles de registros: Paging 3 carga solo las filas visibles en pantalla y las actualiza automáticamente ante cambios en la base de datos.
Use Paging 3 con Room para mostrar feeds de noticias, registros de operaciones o listas de productos con acceso sin conexión y desplazamiento infinito.
Preguntas frecuentes
Room automatiza la creación de tablas, la conversión de Cursor a objetos y la verificación de SQL en tiempo de compilación. SQLiteOpenHelper requiere escribir el esquema manualmente, manejar Cursor y no tiene verificación de consultas antes de ejecutar la aplicación, lo que aumenta el riesgo de errores.
Sí, al cambiar una Entity (agregar/eliminar un campo, cambiar un tipo), se requiere una migración. Sin ella, Room lanza una IllegalStateException al iniciar. Para desarrollo, puede habilitar fallbackToDestructiveMigration, pero las versiones de producción requieren scripts de migración correctos.
Room admite @ForeignKey para operaciones en cascada y @Relation para objetos anidados. Para consultas JOIN complejas, use la anotación @Transaction con @Query que devuelve un POJO con entidades anidadas mediante @Embedded y @Relation.
Sí, Room es totalmente compatible con Java. En lugar de funciones suspend, use LiveData u RxJava Observable; en lugar de Flow, use LiveData. Room con Java admite todas las mismas anotaciones, pero requiere más código boilerplate para operaciones asíncronas.
Room admite cifrado mediante SQLCipher de Zetetic. En lugar de Room.databaseBuilder, use SupportFactory de la biblioteca net.zetetic:android-database-sqlcipher, pasando la contraseña de cifrado. Todos los datos en disco se cifrarán de forma transparente para las consultas DAO.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también