web-dev-qa-db-ja.com

Android Room Database DAOデバッグログ

次のようなRoomデータベースDAOがあるとします:

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


}

LoggerまたはMyDaoに追加されたようなものを使用して、どのステートメントが実行されているかを確認する方法はありますか。これは、関数が期待されるSQLステートメントに正しく変換されているかどうかをすぐに確認できるため、開発中に非常に役立ちます。

24
IHeartAndroid

document Roomのように、コンパイル時のチェックを実行するため、SQLステートメントが有効でない場合、コンパイル自体が失敗し、適切なエラーメッセージがログに表示されます。

また、生成されたコードはデフォルトでデバッグ可能であり、下記のパスにあります。

ビルド>生成>ソース> apt>パッケージ> yourDao_Impl.Java

このクラスには、プロジェクト内の他のクラスをデバッグするときにこのクラスをデバッグできるDAOの実装が含まれています。 :-)

例:

enter image description here

13
Pinakin

DAOレベルでは、そのためのフックは存在しないようです。データベースのオープンとアップグレードに関連するコールバックがありますが、任意のものではありません。

ただし、 機能要求を提出する が可能です。私はそれが役に立つかもしれないことに同意します。さらに良いのは、OkHttpスタイルの汎用インターセプターフレームワークです。

12
CommonsWare

ルームdb Androidはデバッグコンソールにエラーを表示しません。デバッグ中に何が起こるかを確認する方法を見つけました。

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

出力は次のとおりです。

UNIQUE制約の失敗:quiz.theme(コード2067)

6
bitvale

RoomがフレームワークのSqliteを基礎となるデータベースとして使用していると仮定すると、ステートメントは非常に簡単に記録できます。唯一の制限:これはemulatorでのみ実行できます。

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

デフォルトでは、log.tag.SQLiteStatementsの値は設定されていません。

alex @ mbpro:〜$adb Shell getprop log.tag.SQLiteStatements
<-空白行->

上記のドキュメントによると、プロパティを設定するには、使用する必要があります。

alex @ mbpro:〜$adbシェルsetprop log.tag.SQLiteStatements VERBOSE
alex @ mbpro:〜$adb Shell getprop log.tag.SQLiteStatements
詳細

ご覧のとおり、VERBOSE値が正常に設定されました。ただし、アプリケーションを再実行すると、これらのステートメントは出力されません。動作させるには、 すべてのサービスを再起動adb Shell stopを使用してからadb Shell startを使用する必要があります。
通常のデバイスでそれを行おうとすると、次のエラーが表示されます(Pixel XL/stock Android 9)で試しました):

alex @ mbpro:〜$adb Shell start
start:ルートである必要があります
alex @ mbpro:〜$adb root
adbdは実動ビルドでrootとして実行できません

これが、エミュレータを使用する必要がある理由です。

alex @ mbpro:〜$adb root
adbdをルートとして再起動
alex @ mbpro:〜$adb Shell stop
alex @ mbpro:〜$adb Shell start

エミュレータが再起動します。
アプリケーションを実行すると、logcatに同様のSqliteステートメントが表示されます。

<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'"  

変更を取り消すには、次のコマンドを使用します。

alex @ mbpro:〜$adbシェルsetprop log.tag.SQLiteStatements\"\"
alex @ mbpro:〜$adb Shell getprop log.tag.SQLiteStatements
<-空白行->
alex @ mbpro:〜$adb Shell stop
alex @ mbpro:〜$adb Shell start
alex @ mbpro:〜$adb unroot
adbdを非ルートとして再起動

6
Alex Lipov

Selectクエリのハックで実現できました。これは挿入/更新/削除操作では機能しません:)

次のように別個のクラスRoomLoggingHelperを作成します

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

または、このファイルを here からダウンロードできます

このファイルをプロジェクトに追加し、次のようにRoom Databaseクラスから呼び出します。queryメソッドの両方をこのようにオーバーライドします

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

免責事項:

  • Reflectionを使用して文字列SQLを取得しているため、DEBUGモードでのみ使用してください
  • これは急いで書かれており、エラーが含まれている可能性があります。try-catchブロック
  • また、文字列引数についてもテストしましたが、長く、二重にも機能するはずで、Blobsには機能しません
3
Dinesh Singh