Android Room数据库DAO调试日志

时间:2017-09-12 09:12:53

标签: android logging android-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语句。

6 个答案:

答案 0 :(得分:12)

在DAO级别似乎没有任何钩子。有与数据库打开和升级相关的回调,但不是任意的东西。

但你可以file a feature request。我同意它可能有用。更好的是OkHttp风格的通用拦截器框架。

答案 1 :(得分:12)

根据Room的document,它执行编译时间检查,因此如果您的SQL语句无效,编译本身就会失败并且日志中会显示正确的错误消息。

默认情况下,生成的代码也是可调试的,可以在下面提到的路径中找到。

  

构建&gt;生成&gt;来源&gt; apt&gt;你的包&gt; yourDao_Impl.java

此类包含DAO的实现,您可以在调试项目中的其他类时调试此类。 : - )

示例:

enter image description here

答案 2 :(得分:8)

截至 Room 2.3.0-alpha04(2020 年 12 月 16 日发布,在您阅读本文时可能已经稳定),Room 直接支持使用新的 RoomDatabase.QueryCallback

记录 SQL 查询

您在 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()
    }

请注意,这只是示例代码,您可能应该确保 MyDatabase 在您的应用中是单例。另一个提示是仅在应用程序处于调试状态时记录查询: if (BuildConfig.DEBUG) dbBuilder.setQueryCallback(... 以及上面的其余代码。

如果有人想要 Java 中的示例代码,请评论

答案 3 :(得分:5)

当我在插入或更新房间内的行时遇到一些未知错误时,Android在调试控制台中没有显示任何错误。有一件事我发现如何在调试时检查是什么:

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

输出是:

  

UNIQUE约束失败:quiz.theme(代码2067)

答案 4 :(得分:2)

我已经能够通过针对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数据库类中调用它,如下所示:  覆盖这两个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)
    }

免责声明:

  • 我正在使用反射来获取字符串SQL,因此仅在调试模式下使用此命令
  • 这是草率编写的,可能包含错误,将其保留在try-catch块中是明智的选择
  • 此外,我已经针对字符串args进行了测试,它应该可以长时间使用,并且也可以工作一倍,不适用于Blobs

答案 5 :(得分:2)

假定Room使用框架的Sqlite作为基础数据库,则可以很简单地记录语句。唯一的限制:只能在模拟器上完成此操作。

来自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 shell setprop log.tag.SQLiteStatements VERBOSE
  alex @ mbpro:〜$ adb shell getprop log.tag.SQLiteStatements
  详细

我们可以看到,VERBOSE值已成功设置。但是,如果我们重新运行我们的应用程序-我们将看不到这些语句。要使其正常工作,我们必须先使用adb shell stop restart all the services,然后使用adb shell start
如果您尝试使用常规设备执行此操作,则会收到以下错误(已在Pixel XL / Android 9上尝试过):

  

alex @ mbpro:〜$ adb shell启动
  开始:必须是root
  alex @ mbpro:〜$ adb根
  adbd无法在生产版本中以root用户身份运行

这就是为什么我们必须使用模拟器:

  

alex @ mbpro:〜$ adb根
  以root身份重新启动adbd
  alex @ mbpro:〜$ adb shell停止
  alex @ mbpro:〜$ adb shell启动

模拟器将重新启动。
运行您的应用程序,您将在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 shell setprop log.tag.SQLiteStatements \“ \”
  alex @ mbpro:〜$ adb shell getprop log.tag.SQLiteStatements
  <-空白行->
  alex @ mbpro:〜$ adb shell停止
  alex @ mbpro:〜$ adb shell启动
  alex @ mbpro:〜$ adb取消root
  以非root身份重新启动adbd