如何在 slick 中使用事务

问题描述:

我有这样的插入方法(权重是索引)

I have insert method like this (weight is index)

implicit def run[A](action: DBIOAction[A, NoStream, _ <: slick.dbio.Effect]): Future[A] = {
    db.run(action)
  }

def insert(newCategory: CategoryExtractor): Future[Either[String, CategoryResponse]] = {
        category.map(_.weight).max.result.flatMap {
          case Some(weight) =>
            val temp = newCategory.copy(weight = weight+1)
            (category += temp).andThen(DBIO.successful(Right(toCategoryExtractor(temp))))
          case None =>
            val temp = newCategory.copy(weight = 1)
            (category += temp).andThen(DBIO.successful(Right(toCategoryExtractor(temp))))
        }
  }

我称之为 2 次​​p>

and I call it 2 time

insert(CategoryExtractor("1", "name", "scala every where", 0, 0, 0, None)) onComplete {
    case Success(data) => println(data)
  }

insert(CategoryExtractor("2", "name", "haskell every where", 0, 0, 0, None)) onComplete {
    case Success(data) => println(data)
  }

它返回异常(唯一索引).

and it return exception(Unique index).

如何解决这个问题,我不使用 for-comprehension 或插入第一个 onComplete.只需调用 2 次.

How to fix this and I don't use for-comprehension or insert in first onComplete. Just call it 2 time.

谢谢.

这是一个常见的错误 - 转换为 Future 到早期(换句话说,调用 db.run(...) 到早期).

That is a common mistake - converting to Future to early (in other words calling db.run(...) to early).

您需要做的是删除此方法,因为它(可能有点不直观)弊大于利:

What you need to do is to remove this method as it (maybe a little unintuitively) brings more harm than good:

implicit def run[A](action: DBIOAction[A, NoStream, _ <: slick.dbio.Effect]): Future[A] = {
    db.run(action)
  }

经验法则基本上是您通常希望严格控制实际的数据库交互(和事务边界),因此我建议不要在该领域使用任何类型的隐式.毕竟这是这个库背后的驱动思想之一 - Slick 试图在应用程序和数据库之间的交互中非常明确(与经典 ORM 相反 - 使用访问器可能实际上触发了对 db 或通过 mutator 设置值可能会导致实际的数据库更新).

Rule of thumb is basically that you usually would like to strictly control you actual database interactions (and transactional boundaries) so I would advice against any kind of implicits in this area. After all it's one of the driving ideas behind this library - Slick tries to be very explicit in your interactions between application and db (contrary to classic ORMs - where using accessor might have actually fired lazy call to db or setting a value through mutator might resulted in actual database update).

然后你需要把返回类型改成这个(把Future改成DBIO):

Then you need to change the return type to this (changed Future to DBIO):

def insert(newCategory: CategoryExtractor): DBIO[Either[String, CategoryResponse]] = {
...
  }

然后你这样做:

val firstInsert = insert(CategoryExtractor("1", "name", "scala every where", 0, 0, 0, None)) map {
    data => println(data)
}

val secondInsert = insert(CategoryExtractor("2", "name", "haskell every where", 0, 0, 0, None)) map {
    data => println(data)
}

db.run(DBIO.seq(firstInsert, secondInsert).transactionally))

基本上问题是:一旦您将 DBIO 转换为 Future,您就失去了将操作捆绑到单个事务中的能力.所以您基本上使用不同的 DBIO 转换(通常的东西:mapflatMapseq 等)来做所有事情,并且只作为你触发的最后一步db.run(yourComposedDbio.transactionally).

Basically the thing is: as soon as you convert DBIO to Future you loose capability of bundling actions into single transaction. So you basically do everything with use of different transformations of DBIO (usual stuff: map, flatMap, seq etc) and only as the very last step you fire db.run(yourComposedDbio.transactionally).

以下是我几周前的演示文稿中有关处理事务和 DBIO 组合的更多信息.相关幻灯片:http://slides.com/pdolega/slick-101#/85(以及更多).

Here is some more information about handling transactions and DBIO composition from the presentation I gave couple of weeks ago. Relevant slide: http://slides.com/pdolega/slick-101#/85 (and further).

还有一个由 Dave Gurnell 主持的很棒的研讨会,他在 01:05:00 左右谈到了这个问题(链接在这里:https://vimeo.com/148074461 )

Also there is a great workshop run by Dave Gurnell where he talks about this at around: 01:05:00 (link here: https://vimeo.com/148074461 )