Android Room 数据库 DAO 调试日志
Posted
技术标签:
【中文标题】Android Room 数据库 DAO 调试日志【英文标题】:Android Room Database DAO debug log 【发布时间】:2018-02-20 16:22:15 【问题描述】:给定这样的 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);
有没有办法将记录器或类似的东西添加到MyDao
,以便我可以看到正在执行哪些语句。这在开发过程中非常有用,因为我可以立即检查函数是否正确转换为预期的 SQL 语句。
【问题讨论】:
你找到解决办法了吗? @MehulJoisar 我已经在下面发布了我的答案,它对我有用。可能会帮到你。 【参考方案1】:假设 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 shell setprop log.tag.SQLiteStatements VERBOSE alex@mbpro:~$ adb shell getprop log.tag.SQLiteStatements 详细的
如我们所见,VERBOSE
值已成功设置。但是,如果我们重新运行我们的应用程序 - 我们将不会看到打印这些语句。为了使它工作,我们必须使用restart all the services adb shell stop
然后adb shell start
。
如果您尝试使用普通设备执行此操作,您将收到以下错误(已尝试使用 Pixel XL/stock Android 9):
alex@mbpro:~$ adb shell 启动 开始:必须是根 alex@mbpro:~$ adb root adbd 无法在生产版本中以 root 身份运行
这就是我们必须使用模拟器的原因:
alex@mbpro:~$ adb root 以 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 unroot 以非 root 身份重新启动 adbd
【讨论】:
这对我非常有帮助。谢谢。 您在答案开头放置的布尔方法。这段代码去哪儿了? @AndroidDev123 我猜你指的是DEBUG_SQL_STATEMENTS
。你不直接调用它(框架会),我添加它只是作为对上面写的评论的引用。
@AndroidDev123 根据this description,您应该使用允许提升权限的 AOSP 系统映像。
这很棒。正是我想要的。我现在可以精确地调试所有数据库操作!谢谢!!!!!!【参考方案2】:
在 DAO 级别似乎没有任何挂钩。有与数据库打开和升级相关的回调,但不是任意的。
不过,您可以file a feature request。我同意它可能有用。更好的是 OkHttp 风格的通用拦截器框架。
【讨论】:
由于似乎没有任何其他功能请求:issuetracker.google.com/issues/74877608 该功能似乎仍未实现。获取日志的任何其他解决方法? @MehulJoisar:Yiğit 在issuetracker.google.com/issues/74877608#comment4 中有一个有趣的角度,那就是编写一系列支持数据库类来处理日志记录。不过,我不知道有谁这样做过。 @CommonsWare 如何在我的应用程序中使用它?请分享一些想法。 @MehulJoisar:关于如何创建一组自定义支持数据库 API 类的说明远远超出了 Stack Overflow 答案的范围,更不用说评论了。简而言之,您需要创建多个接口的实现(例如,SupportSQLiteDatabase
),将它们连接在一起,并拥有一个SupportSQLiteOpenHelper.Factory
以便能够将它们应用于RoomDatabase.Builder
。我在CWAC-SafeRoom 中执行此操作,以支持 SQLCipher。【参考方案3】:
根据 Room 的document,它会执行编译时检查,因此如果您的 SQL 语句无效,则编译本身会失败并在日志中显示正确的错误消息。
默认情况下生成的代码也是可调试的,可以在下面提到的路径下找到。
build > generated > source > apt > your Package > yourDao_Impl.java
此类包含 DAO 的实现,您可以在调试项目中的其他类时调试此类。 :-)
例子:
【讨论】:
感谢您的回复。我自己在调试时看到了_Impl
,但我找不到一个完整的字符串,其中所有占位符都已经填充了参数。也许我错过了?
@IHeartAndroid 有没有找到提供日志的解决方案?【参考方案4】:
截至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 中的示例代码,请发表评论
【讨论】:
如果您决定使用 Log.xxx 方法,请记住,消息有最大字符长度限制。因此,如果您缺少 Logcat 中的某些查询,请尝试将 sqlQuery 拆分为多个日志条目。 (这里有一些例子 [***.com/questions/8888654/…) 现在是稳定版2.3.0
[developer.android.com/jetpack/androidx/releases/….
@georgiecasey,你能帮忙分享一下Java代码吗?提前致谢。
@georgiecasey,我已经能够使其与 Java 代码一起使用。但看起来都是占位符“?”在 SQL 中。反正有没有显示实际执行的SQL?这 ”?”必须是特定的值。【参考方案5】:
当我在房间数据库中插入或更新行时遇到一些未知错误时,Android 不会在调试控制台中显示任何错误。我发现如何检查调试时发生的一件事是:
try someSource.update(someRow) catch (e: Throwable) println(e.message)
输出是:
UNIQUE 约束失败:quiz.theme(代码 2067)
【讨论】:
在哪个地方? ViewModel / Repo 等!! @Abhiroop Nandi Ray,它只用于调试,在你想要的地方。如果您使用此代码:尝试 yourDao.getAllRowsFromDB() catch ... 并得到一些异常,它将在 catch 块中被捕获。 不错的方法,拯救了我的一天!【参考方案6】:我已经能够通过对 Select 查询的 hack 来实现它。这不适用于插入/更新/删除操作:)
创建一个单独的类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下载此文件
将此文件添加到您的项目并从您的房间数据库类中调用它,如下所示:
像这样覆盖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 对其进行了测试,应该也可以使用 long 和 double,但不适用于 Blobs
【讨论】:
我会检查一下,它是否也可以用于生产? @MehulJoisar 您应该只在调试版本中使用它,而不是在生产版本中使用它,因为我正在使用反射,它可能会影响性能。您可以点击此链接了解更多详情:***.com/questions/23844667/… 我正在使用@DineshSingh 代码示例,但是我的自定义查询有问题,所以我进行了以下更改:if (argList.size == 0) return; query.sql var sql = query.sql argList.forEach sql = sql.replaceFirst("?", it) return sql
而不是return String.format(query.sql.replace("?", "%s"), *argList.toArray())
。要知道您是否在Debug Mode
,可以使用以下语句:if (BuildConfig.DEBUG) val queryString = RoomLoggingHelper.getStringSql(query, args) Log.d("Room", queryString) return super.query(query)
以上是关于Android Room 数据库 DAO 调试日志的主要内容,如果未能解决你的问题,请参考以下文章
Android Room Dao:按 CASE 排序不起作用