Совместное использование сеанса базы данных между несколькими методами в Slick 3

Недавно я переключился с Slick-2 на Slick-3. Все работает очень хорошо с slick-3. Однако у меня возникают некоторые проблемы, связанные с транзакциями. Я видел разные вопросы и пример кода, в котором transactionally и withPinnedSession используются для обработки транзакции. Но мое дело немного другое. Как transcationally, так и withPinnedSession можно применять на Query. Но то, что я хочу сделать, - это передать один и тот же сеанс другому методу, который будет выполнять некоторые операции и захочет обернуть несколько методов в одной транзакции.

У меня есть следующий код slick-2, я не уверен, как это можно реализовать с помощью Slick-3.

def insertWithTransaction(row: TTable#TableElementType)(implicit session: Session) = {
      val entity = (query returning query.map(obj => obj) += row).asInstanceOf[TEntity]
      // do some operations after insert
      //eg: invoke another method for sending the notification
      entity
}

override def insert(row: TTable#TableElementType) = {
    db.withSession {
      implicit session => {
        insertWithTransaction(row)
      }
    }
}

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

Например,

db.withTransaction { implicit session =>
    insertWithTransaction(row1)
    insertWithTransaction(row2)
    //check some condition, invoke session.rollback if something goes wrong
}

Но с помощью slick-3 транзакционная транзакция может применяться только по запросу. Это означает, что, когда нам нужно сделать некоторую логику централизованно после вставки, это возможно. Каждый разработчик должен вручную обрабатывать эти сценарии явно, если они используют транзакции. Я считаю, что это может привести к ошибкам. Я пытаюсь абстрагировать всю логику в операции вставки, так что разработчикам нужно беспокоиться только об успешности/неудаче транзакции

Есть ли другой способ, в slick-3, в котором я могу передать один и тот же сеанс нескольким методам, чтобы все можно было сделать в одиночном сеансе db.

Ответ 1

Вам что-то не хватает: .transactionnaly не относится к Query, а к DBIOAction. Тогда a DBIOAction может состоять из нескольких запросов, используя монадическую композицию.

Вот пример из документации:

val action = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

action состоит из запроса select и как много запросов delete в виде строк, возвращаемых первым запросом. Все это создает DBIOAction, который выполняется в транзакции.

Затем, чтобы запустить действие против базы данных, вы должны вызвать db.run, так что вот так:

val f: Future[Unit] = db.run(action)

Теперь, чтобы вернуться к вашему примеру, скажем, вы хотите применить запрос update после своей вставки, вы можете создать действие таким образом

val action = (for {
  entity <- (query returning query.map(obj => obj) += row)
  _ <- query.map(_.foo).update(newFoo)
} yield entity).transactionnaly

Надеюсь, что это поможет.