Транзакции базы данных Android Room

С новой базой данных комнат в Android у меня есть требование, в котором необходимо выполнить две последовательные операции:

removeRows(ids);
insertRows(ids);

Если я запустил это, я вижу (при изучении базы данных), что отсутствуют некоторые строки - я предполагаю, что они удаляются после вставки. а именно первая операция выполняется параллельно со второй.

Если я использую такой блок транзакции, как этот, то все в порядке - кажется, что первая операция завершается раньше, чем вторая:

roomDb.beginTransaction();
removeRows(ids);
roomDb.endTransaction();

insertRows(ids);

Также нормально, если вместо этого я дам поспать между ними:

removeRows(ids);
Thread.sleep(500);

insertRows(ids);

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

РЕДАКТИРОВАТЬ: после указания @CommonsWare @Query являются асинхронными, а @Insert и @Delete - синхронными. В связи с этим, как мне получить запрос, который удаляет строки как асинхронные:

@Query("DELETE from table WHERE id IN(:ids)")
int removeRows(List<Long> ids);

В соответствии с выводом сборки я получаю Deletion methods must either return void or return int (the number of deleted rows), если пытаюсь обернуть возвращаемый тип в Flowable.


person rajath    schedule 23.06.2017    source источник
comment
Каковы именно реализации removeRows() и insertRows()? Если это простые методы @Delete и @Insert DAO, тогда они должны быть сериализованы естественным образом, поскольку эти методы выполняются синхронно. Единственное место, где Room выполняет асинхронные операции, - это @Query с реактивным возвращаемым значением (LiveData, Flowable и т. Д.).   -  person CommonsWare    schedule 23.06.2017
comment
@CommonsWare, да, в то время как insertRows() просто @Insert, у removeRows() есть @Query вызовов. Думаю, это объясняет. Итак, я полагаю, что ответ на мой вопрос - подписаться на реактивный ответ на запросы.   -  person rajath    schedule 24.06.2017
comment
@CommonsWare, спасибо за помощь. Я отредактировал вопрос с учетом вашего комментария. Как я могу написать @Query, который выполняет DELETE, чтобы я мог наблюдать за ним до завершения?   -  person rajath    schedule 24.06.2017
comment
@Query, который возвращает int, должен быть синхронным. Как я уже писал, единственное место, где Room выполняет асинхронные операции, - это @Query с реактивным возвращаемым значением (LiveData, Flowable и т. Д.). Возможно, это ошибка где-то в комнате. Есть ли особая причина, по которой вы используете @Query, а не @Delete? @Delete уже предлагает IN поддержку списка идентификаторов.   -  person CommonsWare    schedule 24.06.2017
comment
Ну, я просто упростил код в вопросе. На самом деле removeRows - это только одна из двух операций удаления, которые я выполняю. У другого есть более сложный запрос, который, если вы думаете, что это поможет, я могу добавить к вопросу. Можете ли вы также сказать или указать мне, где @Delete предлагает IN поддержку списка идентификаторов?   -  person rajath    schedule 24.06.2017
comment
@Insert, @Update и @Delete принимают один идентификатор, набор идентификаторов или несколько аргументов идентификаторов. developer.android.com/topic/libraries/architecture/   -  person CommonsWare    schedule 24.06.2017
comment
Но в https://developer.android.com/reference/android/arch/persistence/room/Delete.html, в нем говорится, что все параметры метода Delete должны быть либо классами, аннотированными Entity, либо его коллекциями / массивом. Вы можете показать мне синтаксис, используемый для передачи набора идентификаторов?   -  person rajath    schedule 24.06.2017
comment
Приношу свои извинения - здесь рано ... :-) Да, этим методам нужны сущности, а не идентификаторы, поэтому методы знают, из какой таблицы удалять. Я подумал о том, чтобы заполнить запрос функции, чтобы мы могли указать класс сущности в аннотации и принять идентификаторы в качестве параметров, хотя я не думаю, что закончил регистрацию этого. Опять же, извините за смущение.   -  person CommonsWare    schedule 24.06.2017
comment
Все в порядке. Итак, похоже, что нет простого решения (если я @Query все сущности и не передам их @Delete, но это сильно скажется на производительности. Может быть, этот запрос функции - хорошая идея :-)   -  person rajath    schedule 24.06.2017
comment
Что ж, опять же, @Query возврат int должен быть синхронным, поскольку это не реактивное возвращаемое значение. Попробуйте поставить точку останова там, где вы вызываете removeRows(), затем выполните сгенерированный код и посмотрите, выполняется ли запрос синхронно или асинхронно. Вы можете сделать то же самое со своим insertRows(). Если один или другой является асинхронным (чего быть не должно), это поможет объяснить ваши симптомы.   -  person CommonsWare    schedule 24.06.2017
comment
Если оба выполняются синхронно, то что-то где-то серьезно запуталось, чтобы объяснить ваши предыдущие результаты, и нам понадобится воспроизводимый тестовый пример, чтобы разобраться в этом.   -  person CommonsWare    schedule 24.06.2017
comment
Конечно. Я проверю это и вернусь.   -  person rajath    schedule 24.06.2017
comment
@CommonsWare, вы правы - в мой код был встроен вызов @Query, на основе которого мне пришлось выполнить некоторые вычисления и удалить некоторые строки. Этот предыдущий вызов был асинхронным и вызывал проблемы. @Delete и @Insert называют себя синхронными. Большое спасибо за вашу помощь.   -  person rajath    schedule 25.06.2017
comment
@Rajath: Значит, вызов '@Query' по умолчанию выполняется асинхронно или вы запускаете его явно в фоновом потоке? AFAIK, поскольку '@Query' не возвращает никаких наблюдаемых, по умолчанию он не будет работать в фоновом потоке.   -  person Amit Vikram Singh    schedule 19.07.2018
comment
@rajath Я думаю, что @Delete (а также @Update) ищет строки на основе первичного ключа. Итак, если вы установите свой идентификатор как @PrimaryKey, вы создадите фиктивный объект с желаемым идентификатором в качестве ключа, а затем передадите его функции удаления.   -  person Sourav Kannantha B    schedule 07.05.2021
comment
Да, работает developer.android.com/training/ хранилище данных / комната /   -  person Sourav Kannantha B    schedule 07.05.2021


Ответы (5)


Как указано в документации для транзакции, вы можете сделать следующее:

 @Dao
 public abstract class ProductDao {
    @Insert
    public abstract void insert(Product product);

    @Delete
    public abstract void delete(Product product);

    @Transaction
    public void insertAndDeleteInTransaction(Product newProduct, Product oldProduct) {
         // Anything inside this method runs in a single transaction.
         insert(newProduct);
         delete(oldProduct);
     }
 }
 
person guness    schedule 12.10.2017
comment
что, если я использую интерфейсы? - person johnny_crq; 25.10.2017
comment
@johnny_crq Я использовал интерфейсы, и переключиться на абстрактные классы было несложно. в качестве альтернативы вы можете попробовать этот уродливый трюк с интерфейсом @Transaction @Query("DELETE FROM products; INSERT INTO products VALUES(x,y,z)") вместо метода. - person guness; 25.10.2017
comment
Как бы вы протестировали это в эспрессо, если вы вызываете @Query сразу после Insert? - person IgorGanapolsky; 14.05.2018
comment
@guness, почему этот трюк может быть уродливым? Это простой синтаксис SQL, я думаю, он удобочитаемый и обслуживаемый, абсолютно нормально. - person avalancha; 20.11.2018
comment
Как насчет ситуации, когда существует связь между двумя объектами, а второй объект находится в другом DAO? - person LukaszTaraszka; 17.03.2020
comment
@LukaszTaraszka Я думаю, что для этой ситуации есть метод на самом db. он должен быть похож на db.startTransaction(); aDao.do(); bDao.do(); db.endTransaction() или некоторые блоки стиля котлина. но делать это нужно вне дао. но вы должны быть осторожны с асинхронными запросами. - person guness; 17.03.2020
comment
вставьте аннотации обновления и удаления, уже запущенные внутри транзакции, их перенос в другую транзакцию не добавляет ей никакой ценности. - person Karthik; 12.11.2020
comment
но они находятся в разных транзакциях, их упаковка заставляет их работать в одной транзакции, нет? - person guness; 12.11.2020

Как указывает @CommonsWare, @Query являются асинхронными, а @Insert, @Delete, @Update - синхронными.

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

roomDB.runInTransaction(new Runnable() {
        @Override
        public void run() {
            removeRows(ids);
            insertRows(ids);
        }
    });

Надеюсь, это решит вашу проблему.

person Pinakin Kansara    schedule 08.09.2017
comment
PCMIIW: запросы не всегда асинхронны. Запрос является асинхронным только тогда, когда вы возвращаете наблюдаемое, например. Flowable или LiveData. Поскольку в вопросе Query используется для удаления элемента, возвращаемое значение - int, и, следовательно, он будет выполняться синхронно. - person Amit Vikram Singh; 19.07.2018
comment
Для всех, кому интересно: runInTransaction: [...] Транзакция будет помечена как успешная, если в Runnable не возникнет исключение. - person FirstOne; 31.08.2018

Для транзакций комнат в Котлине вы можете использовать:

  • Интерфейс с реализованным методом, например:
@Dao 
interface Dao {

    @Insert 
    fun insert(item: Item)

    @Delete 
    fun delete(item: Item)

    @Transaction
    fun replace(oldItem: Item, newItem: Item){
        delete(oldItem)
        insert(newItem)
    }

}
  • Или используйте функцию open, например:
@Dao 
abstract class Dao {

    @Insert 
    abstract fun insert(item: Item)

    @Delete 
    abstract fun delete(item: Item)

    @Transaction
    open fun replace(oldItem: Item, newItem: Item){
        delete(oldItem)
        insert(newItem)
    }

}

Вы получите error: Method annotated with @Transaction must not be private, final, or abstract. без модификатора open.

person Daniel    schedule 28.01.2020

Я считаю, что когда мы используем интерфейсы DAO, мы все же можем выполнять транзакцию, используя методы интерфейса по умолчанию. Нам нужно добавить аннотацию @JvmDefault и @Transaction, и мы можем выполнить любую операцию внутри нее, которая принадлежит одной транзакции.

@Dao
interface TestDao {
    @Insert
    fun insert(dataObj: DataType)

    @Update
    fun update(dataObj: DataType): Completable

    @Delete
    fun delete(dataObj: DataType): Completable

    @Query("DELETE FROM $TABLE_NAME")
    fun deleteAllData()

    @Query("SELECT * FROM $TABLE_NAME ORDER BY id DESC")
    fun getAllData(): Single<List<DataType>>

    @JvmDefault
    @Transaction
    fun singleTransaction(dataList: List<DataType>) {
        deleteAllData()
        dataList.forEach {
            insert(it)
        }
    }
}
person AK Ali    schedule 06.08.2019
comment
способ сопряжения с телом !! - person Abdeldjalil Elaaeieida; 18.10.2019
comment
Когда я добавляю @JvmDefault, как вы это делали в своем примере, возникает ошибка. - person André Luiz Reis; 27.02.2021
comment
Не могли бы вы уточнить, какую ошибку вы получаете? - person AK Ali; 28.02.2021

вот решение этой проблемы:

@Query("SELECT * FROM friend WHERE id = :id")
Friend getFriendByID(int id);
@Delete
void delete(Friend friend);

Friend friendToBeDeleted = friendDAO.getFriendByID(id);
friendDAO.delete(friendToBeDeleted);

Вам предстоит пройти два шага!

person David    schedule 26.01.2019
comment
Это две независимые транзакции. - person Cililing; 29.09.2019