Журнал отладки DAO базы данных 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);


}

Есть ли способ добавить регистратор или что-то подобное в MyDao, чтобы я мог видеть, какие операторы выполняются. Это было бы действительно полезно во время разработки, потому что я мог сразу проверить, правильно ли преобразованы функции в ожидаемый оператор SQL или нет.


person JoachimR    schedule 12.09.2017    source источник
comment
Вы нашли какое-нибудь решение?   -  person Mehul Joisar    schedule 20.10.2018
comment
@MehulJoisar Я опубликовал свой ответ ниже, у меня это сработало. Могу помочь тебе.   -  person Dinesh Singh    schedule 09.12.2018


Ответы (6)


Предполагая, что Room использует Sqlite фреймворка в качестве базовой базы данных, операторы могут быть довольно просто зарегистрированы. Единственное ограничение: это можно сделать только на эмуляторе.

Из :

/**
 * 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, а затем adb shell start.
Если вы Попробую сделать это на обычном устройстве, вы получите следующую ошибку (пробовал с Pixel XL / стоковым Android 9):

alex @ mbpro: ~ $ adb shell start
start: должен быть root
alex @ mbpro: ~ $ adb root
adbd не может запускать как root в производственных сборках

Вот почему мы должны использовать эмулятор:

alex @ mbpro: ~ $ adb root
перезапуск adbd как root
alex @ mbpro: ~ $ adb shell stop
alex @ mbpro : ~ $ запуск оболочки adb

Эмулятор перезапустится.
Запустите приложение, и вы увидите аналогичные операторы Sqlite в 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'"  

Чтобы отменить изменения, используйте эти команды:

alex @ mbpro: ~ $ adb shell 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 как не root

person Alex Lipov    schedule 02.03.2019
comment
Это было для меня очень полезно. Спасибо. - person Joshua W; 08.08.2019
comment
Логический метод, который вы указываете в начале своего ответа. Куда идет этот код? - person AndroidDev123; 23.07.2020
comment
@ AndroidDev123 Думаю, вы имеете в виду DEBUG_SQL_STATEMENTS. Вы не называете это напрямую (это делает фреймворк), я добавил его просто как ссылку на комментарий, написанный над ним. - person Alex Lipov; 23.07.2020
comment
Я изо всех сил пытаюсь понять твой ответ. Что именно мы должны сделать, чтобы увидеть распечатку запроса в журнале? Я не понимаю, о чем все эти команды оболочки $ adb и где это вообще происходит в студии Android? - person AndroidDev123; 23.07.2020
comment
@ AndroidDev123 Вышеупомянутые шаги не требуют изменения кода. Все делается с помощью adb инструмента командной строки, который входит в состав Android SDK и может быть вызван из терминала (точные сведения можно найти в это руководство). - person Alex Lipov; 23.07.2020
comment
Я перешел на adb root, который теперь возвращает его не может быть root в производственных сборках. Как получить root-доступ на эмуляторе? - person AndroidDev123; 23.07.2020
comment
@ AndroidDev123 Вы должны использовать образ системы AOSP, который допускает повышенные привилегии согласно это описание. - person Alex Lipov; 23.07.2020
comment
Кажется, он работает, поскольку я могу видеть все запросы в logcat, но то, что я действительно хотел увидеть, было выводом запроса, таким как имена столбцов с данными в строках - просто просмотр запроса не помогает, поскольку я имею в виду, что это просто тот же запрос, который я написал в DAO - person AndroidDev123; 23.07.2020
comment
Отлично. Именно то, что я искал. Теперь я могу точно отлаживать все операции с базой данных! Спасибо!!!!! - person gMale; 31.07.2020

В соответствии с документом Room он выполняет проверку времени компиляции, поэтому если ваш оператор SQL недопустима, сама компиляция не удалась, и в журнале отображается соответствующее сообщение об ошибке.

Также сгенерированный код можно отлаживать по умолчанию, и его можно найти по указанному ниже пути.

сборка> сгенерированный> источник> apt> ваш пакет> yourDao_Impl.java

Этот класс содержит реализацию вашего DAO, вы можете отлаживать этот класс при отладке других классов в своем проекте. :-)

Пример :

введите описание изображения здесь

person Pinakin Kansara    schedule 12.09.2017
comment
Спасибо за ответ. Я сам видел это _Impl во время отладки, но не смог найти полную строку, в которой все заполнители уже заполнены аргументами. Может я это пропустил? - person JoachimR; 12.09.2017
comment
@IHeartAndroid Вы нашли какое-либо решение, которое предоставляет журналы? - person Mehul Joisar; 20.10.2018

Похоже, что на уровне DAO для этого нет никаких крючков. Есть обратные вызовы, связанные с открытием и обновлением базы данных, но не с произвольными вещами.

Однако вы можете отправить запрос функции. Я согласен, что это может быть полезно. Еще лучше была бы общая структура перехватчика в стиле OkHttp.

person CommonsWare    schedule 12.09.2017
comment
Поскольку, похоже, не было никаких других запросов на функцию: issueetracker.google.com/issues/74877608 - person fast3r; 15.03.2018
comment
похоже, что функция все еще не реализована. Есть ли другой способ получить логи? - person Mehul Joisar; 20.10.2018
comment
@MehulJoisar: Yiğit представил интересную точку зрения на issueetracker.google.com/issues/74877608#comment4, который должен написать серию классов поддержки базы данных, которые будут обрабатывать ведение журнала. Однако я не знаю никого, кто делал бы это для регистрации. - person CommonsWare; 20.10.2018
comment
@CommonsWare, как я могу использовать это в своем приложении? поделитесь, пожалуйста, некоторыми мыслями. - person Mehul Joisar; 27.10.2018
comment
@MehulJoisar: Инструкции по созданию настраиваемого набора классов API базы данных поддержки выходят далеко за рамки ответа на вопрос о переполнении стека, не говоря уже о комментарии. Вкратце, вам нужно создать реализации нескольких интерфейсов (например, SupportSQLiteDatabase), связать их вместе и иметь SupportSQLiteOpenHelper.Factory, чтобы иметь возможность применять их к RoomDatabase.Builder. Я делаю это в CWAC-SafeRoom, в моем случае для поддержки SQLCipher. - person CommonsWare; 27.10.2018
comment
@MehulJoisar: В этом примере проекта у меня есть подмножество этих интерфейсов, которого достаточно, чтобы обернуть поставляемый API базы данных поддержки, чтобы вы могли упаковать базу данных как актив. Набор протоколирования классов базы данных поддержки будет напоминать последний, передавая реальную работу другой реализации классов базы данных поддержки, но также выполняя некоторую регистрацию. - person CommonsWare; 27.10.2018
comment
@CommonsWare спасибо, что поделились информацией. Похоже, что потребуется много настроек, чтобы просто внедрить промежуточное ПО для ведения журнала. Я буду смотреть в него. Спасибо! - person Mehul Joisar; 27.10.2018

Начиная с Room 2.3.0-alpha04 (выпущен в декабре 16 2020, может быть стабильным к тому моменту, когда вы это читаете), в Room есть прямая поддержка для регистрации SQL-запросов с новым RoomDatabase.QueryCallback

Вы устанавливаете этот обратный вызов на 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 является синглтоном в вашем приложении. Еще один совет - запросы журнала только тогда, когда приложение DEBUG: _5 _... и остальной код сверху.

Прокомментируйте, если кому-то нужен пример кода на Java

person georgiecasey    schedule 15.02.2021
comment
Если вы решите использовать метод Log.xxx, имейте в виду, что для сообщения существует ограничение на максимальную длину символа. Поэтому, если вам не хватает некоторых запросов в вашем Logcat, попробуйте разбить sqlQuery на несколько записей журнала. (несколько примеров здесь [stackoverflow.com/questions/8888654/) - person Almighty; 22.05.2021

Когда у меня возникла неизвестная ошибка при вставке или обновлении строки в комнате db, Android не показывает никаких ошибок в консоли отладки. Одна вещь, которую я нашел, как проверить, что происходит во время отладки:

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

Выход:

Ошибка ограничения UNIQUE: quiz.theme (код 2067)

person bitvale    schedule 17.10.2017
comment
В каком месте? ViewModel / Repo и т. Д. !! - person Abhiroop Nandi Ray; 06.08.2018
comment
@Abhiroop Nandi Ray, Это только для отладки, там где хочешь. Если вы используете этот код: попробуйте {yourDao.getAllRowsFromDB ()} catch ... и получите какое-то исключение, оно будет поймано в блоке catch. - person bitvale; 06.08.2018
comment
Хороший подход, спас мне день! - person moallemi; 15.05.2019

Мне удалось добиться этого с помощью взлома для запросов 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
    }
}

Или вы можете загрузить этот файл с здесь

Добавьте этот файл в свой проект и вызовите его из класса базы данных комнат следующим образом: Переопределите оба метода 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
person Dinesh Singh    schedule 09.12.2018
comment
Я проверю, будет ли он работать и на продакшене? - person Mehul Joisar; 10.12.2018
comment
@MehulJoisar Вы должны использовать это только в отладочной сборке, а не в производственной сборке, потому что я использую отражение, это может повлиять на производительность. Вы можете перейти по этой ссылке для получения более подробной информации: stackoverflow.com/questions/23844667/ - person Dinesh Singh; 10.12.2018
comment
Я использую пример кода @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) - person Zbarcea Christian; 12.03.2019