Transactions and Room
By default, SQLite treats each individual SQL statement as an individual transaction. To the extent that Room winds up generating multiple SQL statements in response to our annotations, it is Room’s responsibility to wrap those statements in a suitable transaction.
However, sometimes, you have business logic that requires a transaction, for operations that require multiple DAO functions. For example, persisting an invoice might involve inserting an Invoice
and all of its InvoiceLineItem
objects, and that might require more than one DAO function to achieve.
Room offers two ways of setting up app-defined transactions: the @Transaction
annotation and some functions on RoomDatabase
.
Using @Transaction
Your DAO can have one or more functions that have the @Transaction
annotation. Whatever a @Transaction
-annotated function does is wrapped in a SQLite transaction. The transaction will be committed if the @Transaction
-annotated function does not throw an exception. If it does, the transaction will be rolled back.
There are two places to apply @Transaction
: custom open
functions on an abstract
DAO class, or on @Query
functions.
Custom Functions
Here, the idea is that your @Transaction
-annotated function would make multiple DAO calls to other functions (e.g., ones with @Insert
or @Query
annotations), so that the work performed in those other functions “succeed or fail as a whole”.
Given our fictitious Invoice
example, we might have something like this:
@Dao
abstract class InvoiceStore {
@Insert
fun _insert(invoice: Invoice)
@Insert
fun _insertItems(lineItems: List<InvoiceLineItem>)
@Transaction
open fun insert(invoice: Invoice) {
_insert(invoice)
_insertItems(invoice.getLineItems())
}
}
Here, we still use an insert()
function to insert an Invoice
, but we use that to wrap two DAO calls to insert the Invoice
metadata and insert the InvoiceLineItem
objects.
Note that the function with @Transaction
needs to be open
. Room will generate a concrete implementation of your DAO, either extending your abstract class or implementing your interface. To make @Transaction
work, Room code-generates an overriding function that wraps a call to your implementation in a transaction. However, in Kotlin, concrete functions cannot be overridden without the open
keyword. Leaving that keyword off may result in strange compile error messages.
On @Query Functions
It may seem odd to have to specifically request a transaction on a @Query
-annotated function. After all, the default behavior of SQLite is to have each individual SQL statement be in its own transaction.
However, there are two scenarios called out in the documentation where @Transaction
would be a good idea. One is tied to @Relation
, which we will cover later in the book.
The other is tied to a little-known issue with Android’s SQLite support: things get weird when the result set of a query exceeds 1MB. In that case, using the regular Android SQLiteDatabase
API, the Cursor
that you get back does not contain the full result set. Instead, it contains a “window” of results, and if you position the Cursor
after that window, the query is re-executed to load in the next window. This can lead to inconsistencies, if the database is changed in between those two database requests to populate the window. Room, by default, will load the entire result set into your entities, quickly moving through the windows as needed, but there is still a chance that a database modification occurs while this is going on. Using @Transaction
would help ensure that this is not an issue, by having the entire query — including traversing the windows — occur inside a transaction.
Using RoomDatabase
Alternatively, RoomDatabase
offers the same beginTransaction()
, endTransaction()
, and setTransactionSuccessful()
functions that you see on SQLiteDatabase
, and so you use the same basic algorithm:
roomDb.beginTransaction()
try {
// bunch of DAO operations here
roomDb.setTransactionSuccessful()
}
finally {
roomDb.endTransaction()
}
The advantage to this approach is that you can put the transaction logic somewhere other than the DAO, if that would be more convenient or make more sense for your particular implementation. However, it is a bit more work.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.