斯卡拉光滑。事务性插入批处理

Scala Slick. Transactionally insert batch

我已经有了 create 单个实体的方法:

override def createEntity(entity: Entity) =
  db.run(
    ( for {
      existing <- Entity.filter(e => e.id === entity.id).result  //Check, if entity exists
      e <- if (existing.isEmpty)
             (Entity returning Entity) += Entity(entity.id, entity.name)
           else {
             throw new DuplicateException(s"Create failed: entity already exists")
          }
        } yield e ).transactionally
      )

如何重用此方法以事务方式创建实体列表?

//Doesn't work
override def createEntities(entities : List[model.Entity]) = {
    db.run(
      ( for {
          e <- entities
        }
        yield createEntity(e)
      ).transactionally
    )
}

我是 slick 的新手 :(

P.S.对不起我的英文。

我猜你正在寻找这样的东西: http://slick.lightbend.com/doc/3.2.0/dbio.html#sequential-execution

我没有测试代码,不习惯油嘴滑舌3.x,但我觉得应该是这样的:

override def createEntities(entities : List[model.Entity]) = {
  db.run(DBIO.seq(entities.map(createEntity):_*).transactionally)
}

你的问题的根源是你试图从内部调用的代码中执行多个 inner db.runs(在你的 createEntity 中声明) 外部 db.run(在createEntities中声明)。这些 outerinner db.run 在概念上是不相关的,因此不在同一事务中执行。

一点透视

为了更好地理解正在发生的事情以及如何处理这种情况,我们必须讨论 monads,特别是 DBIOAction(我将尝试从概念上解释正在发生的事情简化和偷工减料,因为严格的解释会花费太多时间,而且与解决方案无关)。

思考 DBIOAction 的一种方式是将其作为一个固定的步骤列表(代码)来解释如何执行查询并从数据库中获取结果。 db.run 实际执行此列表。

需要注意的重要一点是,此列表包含要在数据库(SQL 查询)以及本地 jvm(运行时 Scala 函数对象)中执行的步骤(代码)。

此外,此列表(一段代码)的每个元素都依赖于上一个元素,要么从上一个元素中获取输入,要么由上一个元素生成。这一切都通过一系列 map/flatMaps 粘合在一起(for 表达式被脱糖)。这看起来像这样:

(1) sql code (generates input for (2))
(2) jvm code (gets input from (1), and generates (3))
(3) sql code (generates input for (4))
(4) jvm code (gets input from (3), and generates (5))
...

请注意,普通的 jvm 代码被编织到这个列表中,可以是任何东西,只要它为下一步生成指令(或者在代码传递给 map 的情况下交付最终结果)。

这通常赋予 monads(因此 DBIOAction)巨大的表达能力,因为这可以实现整个列表的动态行为(即每个 "jvm code" 步骤都可以影响未来的计算)。

您可以在 "jvm code" 步骤中执行任何操作的副作用是您还可以生成并执行新的不相关的计算列表(这就是您正在尝试做的),这可能没问题但如果您不考虑单子组合,也会感到困惑。

那么,您实际上可以做些什么来解决这个问题呢?

如果您关心代码重用,我建议您摆脱 inner db.runs 并提取 DBIOAction 稍后您可以db.runcreateEntity 和(有一些调整)在 createEntities.

您应该能够将您的代码重写为类似于此的内容(我没有您的实体的确切版本,因此将其视为伪代码):

  def createQuery(entity: Entity) = ( for {
    existing <- Entity.filter(e => e.id === entity.id).result
    e <- if (existing.isEmpty)
      (Entity returning Entity) += Entity(entity.id, entity.name)
    else {
      throw new DuplicateException(s"Create failed: entity already exists")
    }
  } yield e )


  def createEntity(entity: Entity) = db.run(createQuery(entity))

  def createEntities(entities : List[model.Entity]) = {
    db.run(DBIO.sequence(entities.map(createQuery(_))).transactionally)
  }

注意 DBIO.sequence 组合子对 DBIOAction 列表的应用,并在实际 db.run.

之前对结果应用 transactionally

旁注

如果您可以控制您的架构,我建议您将实施 "unique id" 约束从代码中的实体创建移动到您的数据库。