Dada una base de datos de habitaciones DAO como esta:
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;
import java.util.Date;
import java.util.List;
@Dao
public interface MyDao {
@Query("SELECT * FROM MyTable")
List<MyItem> all();
@Query("SELECT * FROM MyTable WHERE date = :date AND language = :language")
MyItem byDate(Date date, String language);
}
¿Hay alguna manera de tener un registrador o algo así agregado a MyDao
para poder ver qué declaraciones se están realizando. Esto sería realmente útil durante el desarrollo, porque podría verificar de inmediato si las funciones se transforman correctamente a la declaración SQL esperada o no.
alex lipov
Suponiendo que Room utilice Sqlite del marco como base de datos subyacente, las declaraciones se pueden registrar de forma muy sencilla. La única limitación: esto se puede hacer sólo en emulador.
De SQLiteDebug.java:
/** * Controls the printing of SQL statements as they are executed. * * Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE". */ public static final boolean DEBUG_SQL_STATEMENTS = Log.isLoggable("SQLiteStatements", Log.VERBOSE);
Por defecto, el log.tag.SQLiteStatements
El valor de no está establecido:
alex@mbpro:~$ adb shell getprop log.tag.SQLiteStatements
De acuerdo con la documentación anterior, para establecer la propiedad tenemos que usar:
alex@mbpro:~$ adb shell setprop log.tag.SQLiteStatements VERBOSE
alex@mbpro:~$ adb shell getprop log.tag.SQLiteStatements
VERBOSO
Como podemos ver, el VERBOSE
el valor se estableció con éxito. Sin embargo, si volvemos a ejecutar nuestra aplicación, no veremos esas declaraciones impresas. Para que funcione, tendremos que reiniciar todos los servicios usando adb shell stop
y entonces adb shell start
.
Si intenta hacer eso con un dispositivo normal, recibirá el siguiente error (probado con Pixel XL / stock Android 9):
alex@mbpro:~$ inicio de shell adb
inicio: debe ser root
alex@mbpro:~$ adb raízadbd no puede ejecutarse como root en compilaciones de producción
Es por esto que tenemos que usar el emulador:
alex@mbpro:~$ adb raíz
reiniciar adbd como root
alex@mbpro:~$ parada de shell adbalex@mbpro:~$ inicio de shell adb
El emulador se reiniciará.
Ejecute su aplicación y verá declaraciones Sqlite similares en logcat:
<redacted..>
V/SQLiteStatements: <redacted>/my_db: "BEGIN EXCLUSIVE;"
V/SQLiteStatements: <redacted>/my_db: "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)"
V/SQLiteStatements: <redacted>/my_db: "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, "3cb5664b6da264c13388292d98141843")"
V/SQLiteStatements: <redacted>/my_db: "CREATE TABLE IF NOT EXISTS `MyTable` (`id` TEXT NOT NULL, `date` INTEGER, `language` TEXT, PRIMARY KEY(`id`))"
<redacted..>
V/SQLiteStatements: <redacted>/my_db: "BEGIN EXCLUSIVE;"
V/SQLiteStatements: <redacted>/my_db: "PRAGMA temp_store = MEMORY;"
V/SQLiteStatements: <redacted>/my_db: "PRAGMA recursive_triggers="ON";"
V/SQLiteStatements: <redacted>/my_db: "CREATE TEMP TABLE room_table_modification_log(version INTEGER PRIMARY KEY AUTOINCREMENT, table_id INTEGER)"
V/SQLiteStatements: <redacted>/my_db: "COMMIT;"
<redacted..>
V/SQLiteStatements: <redacted>/my_db: "SELECT * FROM MyTable"
V/SQLiteStatements: <redacted>/my_db: "SELECT * FROM MyTable WHERE date = 1551562171387 AND language="en""
Para deshacer los cambios, use estos comandos:
alex@mbpro:~$ adb shell setprop registro.etiqueta.SQLiteStatements “”
alex@mbpro:~$ adb shell getprop log.tag.SQLiteStatements
alex@mbpro:~$ parada de shell adbalex@mbpro:~$ inicio de shell adb
alex@mbpro:~$ adb desrootear
reiniciar adbd como no root
-
Esto fue extremadamente útil para mí. Gracias.
– Josué W.
8 de agosto de 2019 a las 13:54
-
El método booleano que pones al comienzo de tu respuesta. ¿Adónde va este código?
– AndroidDev123
23 de julio de 2020 a las 10:15
-
@ AndroidDev123 Supongo que te refieres a
DEBUG_SQL_STATEMENTS
. No lo llama directamente (el marco lo hace), lo agregué solo como una referencia al comentario escrito arriba.– Alex Lipov
23 de julio de 2020 a las 10:19
-
@ AndroidDev123 Debe usar una imagen del sistema AOSP que permita privilegios elevados, según esta descripción.
– Alex Lipov
23 de julio de 2020 a las 11:37
-
Esto es genial. Exactamente lo que estaba buscando. ¡Ahora puedo depurar todas las operaciones de la base de datos con precisión! ¡¡¡¡¡Gracias!!!!!
– gHombre
31 de julio de 2020 a las 5:01
A partir de Habitación 2.3.0-alpha04 (lanzado el 16 de diciembre de 2020, podría ser estable para cuando esté leyendo esto), hay soporte directo en Room para registrar consultas SQL con el nuevo RoomDatabase.QueryCallback
Establece esta devolución de llamada en el RoomDatabase.Builder
fun getDatabase(context: Context): MyDatabase {
val dbBuilder = Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java, "mydatabase.db"
)
dbBuilder.setQueryCallback(RoomDatabase.QueryCallback { sqlQuery, bindArgs ->
println("SQL Query: $sqlQuery SQL Args: $bindArgs")
}, Executors.newSingleThreadExecutor())
return dbBuilder.build()
}
Tenga en cuenta que este es solo un código de ejemplo y probablemente debería asegurarse MyDatabase
es un singleton en su aplicación. Otro consejo es solo registrar consultas cuando la aplicación está DEBUG:
if (BuildConfig.DEBUG) dbBuilder.setQueryCallback(
… y el resto del código de arriba.
Comenta si alguien quiere el código de ejemplo en Java
-
En caso de que decida utilizar el método Log.xxx, tenga en cuenta que existe un límite máximo de longitud de caracteres para el mensaje. Entonces, si le faltan algunas de las consultas en su Logcat, intente dividir el sqlQuery en varias entradas de registro. (Algunos ejemplos están aquí [stackoverflow.com/questions/8888654/…)
– AlmightyMay 22, 2021 at 6:47
-
It’s in stable release
2.3.0
now [developer.android.com/jetpack/androidx/releases/….– MyroslavAug 5, 2021 at 6:36
-
@georgiecasey, could you please help to share the Java code? Thanks in advance.
– g gSep 18, 2021 at 6:42
-
@georgiecasey, I’ve been able to make it work with Java code. But looks it’s all placeholders “?” in the SQL. Is there anyway to display the actually executed SQL? The “?” has to be specific value.
– g gSep 18, 2021 at 12:12
There does not appear to be any hooks for that at the DAO level. There are callbacks related to database opens and upgrades, but not arbitrary stuff.
You could file a feature request, though. I agree that it could be useful. Even better would be an OkHttp-style generic interceptor framework.
-
Since there didn’t seem to be any other feature request: issuetracker.google.com/issues/74877608
– fast3rMar 15, 2018 at 11:36
-
it seems the feature is still not implemented. Any other workaround to get logs?
– Mehul JoisarOct 20, 2018 at 8:38
-
@MehulJoisar: Yiğit had an interesting angle in issuetracker.google.com/issues/74877608#comment4, which is to write a series of support database classes that would handle the logging. I do not know of anyone who has done that for logging, though.
– CommonsWareOct 20, 2018 at 11:49
-
@CommonsWare how can I use that in my application? please share some thoughts.
– Mehul JoisarOct 27, 2018 at 15:55
-
@MehulJoisar: Instructions for how to create a custom set of support database API classes is well beyond the scope of a Stack Overflow answer, let alone a comment. In a nutshell, you need to create implementations of several interfaces (e.g.,
SupportSQLiteDatabase
), wire them together, and have aSupportSQLiteOpenHelper.Factory
to be able to apply them to aRoomDatabase.Builder
. I do this in CWAC-SafeRoom, in my case to support SQLCipher.– CommonsWareOct 27, 2018 at 16:06
As per document of Room it performs compile time check so if your SQL statement is not valid compilation itself failed and proper error message is displayed in the log.
Also generated code is debuggable by default and can be found under below mentioned path.
build > generated > source > apt > your Package > yourDao_Impl.java
This class contains implementation of your DAO you can debug this class as you debug other classes in your project. 🙂
Example :
When I have got some unknown error while inserting or updating row in room db Android does not show any error in debug console. One thing I found how to check whats happen while debug is:
try { someSource.update(someRow) } catch (e: Throwable) { println(e.message) }
Output is:
UNIQUE constraint failed: quiz.theme (code 2067)
-
In which place? ViewModel / Repo etc !!
– Abhiroop Nandi RayAug 6, 2018 at 12:04
-
@Abhiroop Nandi Ray, It’s only for debug, in place where you want. If you use this code: try { yourDao.getAllRowsFromDB() } catch … and get some exception it’ll be caught in catch block.
– bitvaleAug 6, 2018 at 12:33
-
Nice approach, saved my day!
– moallemiMay 15, 2019 at 6:09
I have been able to achieve it via a hack for Select queries. This wont work for insert/update/delete operations 🙂
Create a separate class RoomLoggingHelper
as follows
import android.annotation.SuppressLint
import androidx.room.RoomSQLiteQuery
private const val NULL = 1
private const val LONG = 2
private const val DOUBLE = 3
private const val STRING = 4
private const val BLOB = 5
private const val NULL_QUERY = "NULL"
const val ROOM_LOGGING_TAG = "roomQueryLog"
object RoomLoggingHelper {
@SuppressLint("RestrictedApi")
fun getStringSql(query: RoomSQLiteQuery): String {
val argList = arrayListOf<String>()
val bindingTypes = query.getBindingTypes()
var i = 0
while (i < bindingTypes.size) {
val bindingType = bindingTypes[i]
cuando (bindingType) { NULL -> argList.add(NULL_QUERY) LONG -> argList.add(query.getLongBindings()[i].toString()) DOBLE -> argList.add(query.getDoubleBindings()[i].toString()) CADENA -> argList.add(consulta.getStringBindings()[i].toString()) } i++ } return String.format(query.sql.replace("?", "%s"), *argList.toArray()) } fun getStringSql(query: String?, args: Array?): Cadena? { return if (consulta != nulo && argumentos != nulo) { String.format(consulta.reemplazar("?", "%s"), *argumentos) } else "" } } diversión privada RoomSQLiteQuery.getBindingTypes(): IntArray { return javaClass.getDeclaredField("mBindingTypes").let { field -> field.isAccessible = true return@let field.get(this) as IntArray } } diversión privada RoomSQLiteQuery.getLongBindings(): LongArray { return javaClass.getDeclaredField( "mLongBindings").let { field -> field.isAccessible = true return@let field.get(this) as LongArray } } diversión privada RoomSQLiteQuery.getStringBindings(): Array { return javaClass.getDeclaredField("mStringBindings") .let { field -> field.isAccessible = true return@let field.get(this) as Array } } diversión privada RoomSQLiteQuery.getDoubleBindings(): DoubleArray { return javaClass.getDeclaredField("mDoubleBindings").let { campo -> field.isAccessible = true return@let field.get(this) as DoubleArray } } diversión privada RoomSQLiteQuery.getIntBindings(): IntArray { return javaClass.getDeclaredField("mBi ndingTypes").let { field -> field.isAccessible = true return@let field.get(this) as IntArray } }
O bien, puede descargar este archivo desde aquí
Agregue este archivo a su Proyecto y llámelo desde su clase Room Database de la siguiente manera: Anule ambos query
métodos como este
override fun query(query: SupportSQLiteQuery?): Cursor {
//This will give you the SQL String
val queryString = RoomLoggingHelper.getStringSql(query as RoomSQLiteQuery)
//You can log it in a way you like, I am using Timber
Timber.d("$ROOM_LOGGING_TAG $queryString")
return super.query(query)
}
override fun query(query: String?, args: Array<out Any>?): Cursor {
//This will give you the SQL String
val queryString = RoomLoggingHelper.getStringSql(query, args)
//You can log it in a way you like, I am using Timber
Timber.d("$ROOM_LOGGING_TAG $queryString")
return super.query(query, args)
}
Descargos de responsabilidad:
- Estoy usando Reflection para obtener una cadena SQL, por lo tanto use esto SOLO en modo DEBUG
- Esto está escrito con prisa y puede contener errores, sería prudente mantenerlo en
try-catch
bloquear - Además, lo he probado para argumentos de cadena, debería funcionar para long y double también, no funcionará para
Blobs
-
¿En qué lugar? ViewModel / Repo, etc !!
– Abhiroop Nandi Ray
6 de agosto de 2018 a las 12:04
-
@Abhiroop Nandi Ray, es solo para depurar, en el lugar que desee. Si usa este código: intente { yourDao.getAllRowsFromDB() } catch … y obtenga alguna excepción, quedará atrapado en el bloque catch.
– bitvale
6 de agosto de 2018 a las 12:33
-
¡Buen enfoque, me salvó el día!
– moallemi
15 de mayo de 2019 a las 6:09
¿Has encontrado alguna solución?
– Mehul Joisar
20 de octubre de 2018 a las 8:37
@MehulJoisar He publicado mi respuesta a continuación, funcionó para mí. Podría ayudarte.
– Dinesh Singh
9 dic 2018 a las 15:33