Introducció
Transaccions
Si vols executar més d'una sentència dins una transacció has d'utlitzar la funció transaction()
:
db.transaction {
val eva = db.ownerQueries.insert("Eva").executeAsOne()
val milú = db.dogQueries.insert("Milú", null, null, eva.id).executeAsOne()
println(milú)
}
El resultat és:
Dog(id=7, name=Milú, number=null, breed=null, owner=3)
Per tornar el valor des de una transacció, utilitza la funció transactionWithResult
:
val dogs = db.transactionWithResult {
db.dogQueries.select(5,0).executeAsList()
}
println(dogs)
Rollback
Si durant l'execució del codi es produeix una execpció, automàticamen es farà un "rollback" de la transació.
try {
db.transaction {
db.dogQueries.insert("Kokoro", null, null, null)
throw Exception("Ups!")
}
} catch (e: Exception) {
println("No hi a gossos: ${db.dogQueries.select(5, 0).executeAsList().isEmpty()}")
}
Si vols pots fer un "rollback" de la transacció en qualsevol moment (no es produeix cap excepció):
db.transaction {
db.dogQueries.insert("Kokoro", null, null, null)
rollback()
}
println("No hi a gossos: ${db.dogQueries.select(5, 0).executeAsList().isEmpty()}"
Però si la transacció retorna un valor, has de retornar un valor amb el rollback:
val dogs: Dog? = db.transactionWithResult {
db.dogQueries.insert("Kokoro", null, null, null)
rollback(null)
}
Callbacks
Pots registrar "callbacks" que s'executarn un con la transacció ha finalitzar o s'ha fet un "rollback":
val dog: Dog? = db.transactionWithResult {
afterRollback { println("No s'ha afegit Kokoro") }
afterCommit { println("S'ha afegit Kokoro") }
db.dogQueries.insert("Kokoro", null, null, null)
rollback(null)
}
Grouping Statements
TODO
You can group multiple SQL statements together to be executed at once inside a transaction:
Tipus
https://sqldelight.github.io/sqldelight/2.0.2/jvm_sqlite/types/
Cursor
Però si la llista és molt llarga, i vols processar molts elements de la llista és millor utilitzar un cursor:
val query = db.dogQueries.select(Long.MAX_VALUE,0)
query.execute { cursor ->
while (cursor.next().value) {
val dog = query.mapper(cursor)
println(dog)
}
QueryResult.Unit
}
La manera més fàcil d'accedir a les dades, és mitjançant la funció mapper
que forma part de las classe Query<Dog>
, i que transforma el registre al que apunta el cursor
a un objecte Dog
.
Però si vols, pots accedir als elements de registre de manera posicional:
val query = db.dogQueries.select()
query.execute { cursor ->
while (cursor.next().value) {
println(cursor.getString(1))
}
QueryResult.Unit
}
No tens seguretat a nivell de tipus, però és molt més eficient si no necessites crear l'objecte!
I el que es evident, no cal que recorris tots els elements de la llista!
// Consultem la taula gossos amb màxim 2 resultats
val query = db.dogQueries.select(Long.MAX_VALUE,0)
query.execute { cursor ->
while (cursor.next().value) {
val dog = query.mapper(cursor)
println(dog)
if (dog.name == "Ketzu") {
break
}
}
QueryResult.Unit
}
Listener
Quan estas processant un select
en un entorn concurrent és possible que s'afegeixin o es borrin registres.
Pots registrar un "listener":
val query = db.dogQueries.select(Long.MAX_VALUE,0);
query.addListener {
app.cash.sqldelight.Query.Listener { println("queryResultsChanged") }
}
però només té sentint en un entorn concurrent ...
TODO primer l'alumne ha de saber com s'executa en entorn concurrent.
Activitat
classDiagram direction LR class Team { id integer primary key autoincrement name text not null unique location text not null } class Player { id integer primary key autoincrement name text not null country text not null } Player --> Team : team
Team.sq
CREATE TABLE IF NOT EXISTS Team (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
location TEXT NOT NULL
);
insert:
INSERT INTO Team(name,location) VALUES(?,?);
select:
SELECT * FROM Team LIMIT :limit OFFSET :offset;
selectCount:
SELECT count(*) FROM Team;
Player.sq
CREATE TABLE IF NOT EXISTS Player (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
country TEXT NOT NULL,
team INTEGER NOT NULL,
FOREIGN KEY (team) REFERENCES Team(id)
);
insert:
INSERT INTO Player(name,country,team) VALUES (?,?, ?);
fun insertDevelopmentData(database: Database) {
listOf(
Team(1, "Arsenal", "London"),
Team(2, "Aston Villa", "Birmingham")
).forEach() { team ->
database.teamQueries.insert(team.name, team.location)
}
listOf(
Player(1, "David Raya", "Spain", 1),
Player(2, "William Saliba", "France", 1),
Player(3, "Kieran Tiernay", "Scotland", 1)
).forEach { player ->
database.playerQueries.insert(player.name, player.country, player.team)
}
}
Consultar dades
A continuació has de crear una "Screen" per mostrar tots els equips que estan a la base de dades.
1.- Crea les funcions "composables" TeamView
i TeamViewList
.
@Composable
fun TeamView(team: Team) {
// ...
}
@Composable
fun TeamListView(teams: List<Team>) {
// ...
}
2.- Modifica el setContent
de la classe MainActivity
:
class MainActivity : ComponentActivity() {
// ...
val teams = db.teamQueries.select(Long.MAX_VALUE, 0).executeAsList()
setContent {
TeamListView(teams)
}
}
3.- Crea la funció "composable" TeamListScreen
que té la base de dades com a paràmetre:
@Composable
fun TeamListScreen(database: Database) {
val teams = database.teamQueries.select(Long.MAX_VALUE, 0).executeAsList()
TeamListView(teams)
}
4.- Modifica el setContent
de la classe MainActivity
:
class MainActivity : ComponentActivity() {
// ...
setContent {
TeamListScreen(database)
}
}
Afegir dades
A continuació has de crear una "Screen" per insertar un nou equip a la base de dades
1.- Completa la funció "composable" TeamInsert
per afegir un equip nou.
Has d'afegir un TextField
per location
!
@Composable
fun TeamInsert(handle: (Team) -> Unit) {
val team = remember { mutableStateOf(Team(0,"","")) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
Text(
text = "Team",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(10.dp))
TextField(
value = team.value.name,
placeholder = { Text("name") },
onValueChange = { team.value = team.value.copy(name = it) })
Button(
onClick = {
message.value = handle(team.value)
},
modifier = Modifier.padding(vertical = 40.dp)
) {
Text("Insert")
}
}
}
@Preview
@Composable
fun TeamInsertPreview() {
TeamInsert {
println(it)
}
}
2.- Crea la funció "composable" TeamInsertScreen
.
La funció ha d'insertar el registre a la base de dades.
@Composable
fun TeamInsertScreen(database: Database) {
TeamInsert { team ->
database.teamQueries.insert(team.name,team.location)
}
}
3.- Verifica que pots insertar equips a la base de dades.
4.- Si insertes un equip amb un nom que ja existeix l'aplicació fa "crash".
El motiu és que el nom de l'equip ha de ser únic en la taula Team
.
Modifica les funcion TeamInsert
i TeamInsertScreen
perqué gestioni l'excepció, i mostri a l'usuari un missatge d'error dient que l'equip ja existeix.
@Composable
fun TeamInsert(handle: (Team) -> String) {
val team = remember { mutableStateOf(Team(0, "", "")) }
val message = remember { mutableStateOf("") }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
Text(
text = "Team",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(10.dp))
TextField(
value = team.value.name,
placeholder = { Text("name") },
onValueChange = { team.value = team.value.copy(name = it) })
Spacer(modifier = Modifier.height(10.dp))
TextField(
value = team.value.location,
placeholder = { Text("location") },
onValueChange = { team.value = team.value.copy(location = it) })
Button(
onClick = {
message.value = handle(team.value)
},
modifier = Modifier.padding(vertical = 40.dp)
) {
Text("Insert")
}
Text(
text = message.value,
modifier = Modifier.padding(10.dp),
color = MaterialTheme.colorScheme.error
)
}
}
@Preview
@Composable
fun TeamInsertPreview() {
// Bournemouth, Brentford
TeamInsert {
"Error"
}
}
@Composable
fun TeamInsertScreen(database: Database) {
TeamInsert { team ->
try {
database.teamQueries.insert(team.name, team.location)
"Fet!"
} catch (e: Exception) {
e.message ?: "Error al insertar el registre"
}
}
}