问题
I have a person table and animal table and in the animal table there is FK to personId since there is one-to-many relation between them.
I just want to create a person and create its animals using a transaction cause I want the process to be atomic (there is no use of person in the db if I could not create its animals)
This is the model of how I accept a person creation request:
case class PersonCreateRequest(name: String, age: Int, animals: Seq[AnimalCreateRequest])
This is how the DB knows a Person:
case class Person(personId: Long, name, age: Int)
// this is just a companion object to help me take a PersonCreateRequest and make it Person
object Person {
def apply(person: PersonCreateRequest): Person = {
Person(0L,
person.name,
person.age)
}
}
same thing I have with Animal:
case class AnimalCreateRequest(animalType: String, age: Int)
This is how the db knows a Animal(personId = owner):
case class Animal(animalId: Long, animalType: String, age: Int, personId: Long)
// here I need to get personId as parameter cause I will only have it after a person was created:
object Animal {
def apply(animal: AnimalCreateRequest, personId: Long): Animal = {
Animal(0L,
animal.animalType,
animal.age,
personId)
}
}
So now this is how I tried to do it(and failed):
lazy val ctx = new MysqlAsyncContext(CamelCase, "ctx")
import ctx._
def insertPerson(personToCreate: PersonCreateRequest): Future[Long] = {
// getting the person object that the db knows
val dbPerson = Person.apply(personToCreate)
// INSERT Person Query
val insertPersonQuery = quote {
query[Person].insert(lift(dbPerson)).returning(_.personId)
}
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
contactIds <- {
Future.sequence(
personToCreate.animals.map(animal => {
val animalToInsert = Animal.apply(animal, personId)
insertAnimal(animalToInsert)
})
)
}
} yield personId
}
}
def insertAnimal(animal: Animal): Future[Long] = {
val q = quote {
query[Animal].insert(lift(animal)).returning(_.animalId)
}
ctx.run(q)
}
What happens is that I just dont get a response...its keep processing without returning anything or throwing an error
回答1:
Problem was, currently, Quill async does not support concurrent operations inside transactions.
So had to do the animal insertion sequentially:
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
animals = personCreate.animals.map(Animal.apply(personId, _))
_ <- animals.foldLeft(Future.successful(0l)) {
case (fut, animal) =>
fut.flatMap(_ => insertAnimal(animal))
}
} yield personId
}
also, even better is to use batch insertion :)
Thanks for @fwbrasil and @mentegy for the assistance!
回答2:
Add implicit ExecutionContext
parameter to insertAnimal
method:
def insertAnimal(animal: Animal)(implicit ec: ExecutionContext): Future[Long] =
Without it, you're not passing ec from the transaction block and animal insertions will try and use other connections from the pool.
回答3:
Are you familiar with Scala Futures?
To get a result from a transaction you should add onSuccess
handler to a Future
returned from the ctx.transaction
call:
ctx.transaction { ...
}.onSuccess {
case personId => ...
}
来源:https://stackoverflow.com/questions/48488962/failing-to-use-transactions-in-quill-to-insert-one-to-many-relational-objects