web-dev-qa-db-fra.com

Journal de débogage DAO Android Room Database

Étant donné une base de données de salle DAO comme ceci:

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


}

Existe-t-il un moyen d’avoir un enregistreur ou quelque chose du genre ajouté à MyDao afin que je puisse voir quelles instructions sont exécutées? Cela serait très utile lors du développement, car je pourrais immédiatement vérifier si les fonctions sont correctement transformées en instruction SQL attendue.

16
IHeartAndroid

Il ne semble pas y avoir d’accroche pour cela au niveau de DAO. Il y a des rappels liés aux ouvertures et aux mises à niveau de la base de données, mais pas de choix arbitraire.

Vous pouvez déposer une demande de fonctionnalité , cependant. Je conviens que cela pourrait être utile. Encore mieux serait un framework d'intercepteur générique de style OkHttp.

11
CommonsWare

Conformément à document de Room, il effectue une vérification de la compilation. Par conséquent, si votre instruction SQL n'est pas valide, la compilation elle-même a échoué et le message d'erreur approprié s'affiche dans le journal.

Le code généré est également débogué par défaut et peut être trouvé sous le chemin mentionné ci-dessous.

construire> généré> source> apt> votre paquet> votreDao_Impl.Java

Cette classe contient l’implémentation de votre DAO. Vous pouvez la déboguer à l’aide des autres classes de votre projet. :-)

Exemple :

 enter image description here

11
Pinakin

Lorsque j'ai une erreur inconnue lors de l'insertion ou de la mise à jour d'une ligne dans la base de données, Android ne montre aucune erreur dans la console de débogage. Une chose que j'ai trouvée comment vérifier ce qui se passe pendant le débogage est la suivante:

try { someSource.update(someRow) } catch (e: Throwable) { println(e.message) }

La sortie est: 

La contrainte UNIQUE a échoué: quiz.theme (code 2067)

4
bitvale

J'ai été capable d'y parvenir via un hack pour les requêtes Select. Cela ne fonctionnera pas pour les opérations d'insertion/mise à jour/suppression :)

Créez une classe séparée RoomLoggingHelper comme suit

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]

            when (bindingType) {
                NULL -> argList.add(NULL_QUERY)
                LONG -> argList.add(query.getLongBindings()[i].toString())
                DOUBLE -> argList.add(query.getDoubleBindings()[i].toString())
                STRING -> argList.add(query.getStringBindings()[i].toString())
            }
            i++
        }

        return String.format(query.sql.replace("?", "%s"), *argList.toArray())
    }

    fun getStringSql(query: String?, args: Array<out Any>?): String? {
        return if (query != null && args != null) {
            String.format(query.replace("?", "%s"), *args)
        } else
            ""
    }
}

private fun RoomSQLiteQuery.getBindingTypes(): IntArray {

    return javaClass.getDeclaredField("mBindingTypes").let { field ->
        field.isAccessible = true
        return@let field.get(this) as IntArray
    }
}

private fun RoomSQLiteQuery.getLongBindings(): LongArray {

    return javaClass.getDeclaredField("mLongBindings").let { field ->
        field.isAccessible = true
        return@let field.get(this) as LongArray
    }
}

private fun RoomSQLiteQuery.getStringBindings(): Array<String> {

    return javaClass.getDeclaredField("mStringBindings").let { field ->
        field.isAccessible = true
        return@let field.get(this) as Array<String>
    }
}

private fun RoomSQLiteQuery.getDoubleBindings(): DoubleArray {

    return javaClass.getDeclaredField("mDoubleBindings").let { field ->
        field.isAccessible = true
        return@let field.get(this) as DoubleArray
    }
}

private fun RoomSQLiteQuery.getIntBindings(): IntArray {

    return javaClass.getDeclaredField("mBindingTypes").let { field ->
        field.isAccessible = true
        return@let field.get(this) as IntArray
    }
}

Ou, vous pouvez télécharger ce fichier depuis ici

Ajoutez ce fichier à votre projet et appelez-le à partir de votre classe de base de données Room en procédant comme suit: Remplacez les deux méthodes query comme celle-ci

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

Avertissements:

  • J'utilise Reflection pour obtenir la chaîne SQL, donc je l'utilise uniquement en mode DEBUG
  • Ceci est écrit à la hâte et peut contenir des erreurs, il serait sage de le conserver dans le bloc try-catch
  • En outre, je l'ai testé pour les arguments de chaîne, devrait fonctionner pendant longtemps et double également, ne fonctionnera pas pour Blobs
2
Dinesh Singh

En supposant que Room utilise le Sqlite du framework comme base de données sous-jacente, les instructions peuvent être tout simplement journalisées. La seule limitation: cela ne peut être fait que sur emulator

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

Par défaut, la valeur de log.tag.SQLiteStatements n'est pas définie: 

alex @ mbpro: ~ $ adb Shell getprop log.tag.SQLiteStatements
<- BLANK LINE ->

Selon la documentation ci-dessus, pour définir la propriété, nous devons utiliser: 

alex @ mbpro: ~ $ adb Shell setprop log.tag.SQLiteStatements VERBOSE
alex @ mbpro: ~ $ adb Shell getprop log.tag.SQLiteStatements
VERBEUX

Comme nous pouvons le constater, la valeur VERBOSE a été définie avec succès. Toutefois, si nous réexécutons notre application, nous ne verrons pas ces déclarations imprimées. Pour que cela fonctionne, nous devrons redémarrer tous les services en utilisant adb Shell stop et ensuite adb Shell start.
Si vous essayez de le faire avec un appareil standard, vous recevrez le message d'erreur suivant (essayé avec Pixel XL/stock Android 9): 

alex @ mbpro: ~ $ adb Début du shell
start: doit être root
alex @ mbpro: ~ $ adb root
adbd ne peut pas être exécuté en tant que root dans les versions de production 

C'est pourquoi nous devons utiliser l'émulateur: 

alex @ mbpro: ~ $ adb root
redémarrer adbd en tant que root
alex @ mbpro: ~ $ adb Shell stop
alex @ mbpro: ~ $ adb Début du shell 

L'émulateur va redémarrer.
Exécutez votre application et vous verrez des instructions Sqlite similaires dans 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'"  

Pour annuler les modifications, utilisez ces commandes: 

alex @ mbpro: ~ $ adb Shell setprop log.tag.SQLiteStatements\"\"
alex @ mbpro: ~ $ adb Shell getprop log.tag.SQLiteStatements
<- BLANK LINE ->
alex @ mbpro: ~ $ adb Shell stop
alex @ mbpro: ~ $ adb Début du shell
alex @ mbpro: ~ $ adb unroot
redémarrer adbd en tant que root

0
Alex Lipov