【问题标题】:Slick 3.1.1: Insert entity with its relations in a single transactionSlick 3.1.1:在单个事务中插入实体及其关系
【发布时间】:2017-01-15 22:40:23
【问题描述】:

我有一个 Classified 实体,它有 2 个关系:一个公司和一个位置。为了持久化分类,我需要知道其关系的 id,这可能需要首先持久化一个实体(即它可能已经存在于数据库中,否则应该插入它)。

id 是应用程序分配的 UUID(即它不是由 db 自动递增的),因此应用程序分配一个 id,如果实体已经存在,该 id 最终将成为实体 id 或被事务中的实际 id 替换.

执行此操作的代码如下:

def create(classified: Classified, company: Company, location: Location): Future[String] = {
val interaction = for {
  comp <- companies.filter(_.name === company.name).result.headOption flatMap {
    case None => companies returning companies.map(_.id) += company
    case Some(comp) => DBIO.successful(comp.id.get)
  }
  loc <- locations.filter(_.name === location.name).result.headOption flatMap {
    case None => locations returning locations.map(_.id) += location
    case Some(loc) => DBIO.successful(loc.id.get)
  }
  cl <- classifieds returning classifieds.map(_.id) += classified.copy(companyId = comp, locationId = loc)
} yield cl
db.run(interaction.transactionally)

}

上述方法在针对 Postgres(这是生产数据库)运行时完美运行,但对于 H2(这是测试和开发数据库)失败并出现错误:[SlickException:此 DBMS 只允许从单个 AutoInc 列返回插入]

看起来 H2 驱动程序不会返回 id,除非它们是自动增量类型的。

那么,如何编写此事务,以便 a) 插入发生在单个事务中 b) 以最少的数据库往返和 c) 以数据库中立的方式?

编辑:

上面的方法是从这样的控制器中使用的:

classifiedDao.create(
      Classified(Some(UUID.randomUUID().toString), c.title, Jsoup.clean(c.body, Whitelist.basic()), c.refNo, "", ""),
      Company(Some(UUID.randomUUID().toString), c.companyName, c.companyEmail, None, None),
      Location(Some(UUID.randomUUID().toString), c.location, None)
    ).map(_ =>
      Redirect(routes.Classifieds.form()).flashing("success" -> "Classified submitted")
    )

【问题讨论】:

  • 澄清一下:companylocation 是否已经在插入时分配了 UUID(case None =&gt; companies returning companies.map(_.id) += company 行)?
  • 是的。控制器将 id 分配给所有三个实体,然后将对象传递给 DAO 以进行持久化,如果实体已存储,则丢弃 id 并使用从 db 检索到的 id,而始终使用的 Classified 除外。
  • @Roman 那条评论简直是天才。在 None 情况下,我已经知道 id,我需要做的就是使用其他 DBAction 对结果进行排序,然后我就完成了。谢谢,那太棒了。 PS:我在下面发布答案以供后代使用。

标签: scala h2 slick


【解决方案1】:

事实证明,从数据库中检索 id 是容易的部分,因为执行此操作的代码已经具有所需的类型 (DBIOAction),困难的部分是在插入后获取 id,但在这种情况下,我已经知道id 因为是我的代码设置了它。无需使用返回()并依赖关系数据库管理系统。

解决方案:

def create(classified: Classified, company: Company, location: Location): Future[String] = {
val interaction = for {
  comp <- companies.filter(_.name === company.name).result.headOption flatMap {
    case None => {
      companies += company
      DBIO.successful(company.id.get)
    }
    case Some(comp) => DBIO.successful(comp.id.get)
  }
  loc <- locations.filter(_.name === location.name).result.headOption flatMap {
    case None => {
      locations += location
      DBIO.successful(location.id.get)
    }
    case Some(loc) => DBIO.successful(loc.id.get)
  }
  cl <- {
    classifieds += classified.copy(companyId = comp, locationId = loc)
    DBIO.successful(classified.id.get)
  }
} yield cl
db.run(interaction.transactionally)
}

【讨论】:

  • 很高兴我能帮上忙。您可以考虑使用DBIOAction.andThen 来确保插入成功运行:(companies += company).andThen(DBIO.successful(company.id.get))
【解决方案2】:

要让 Slick returning 工作 Id 必须是 auto generated primary key

Id不仅应该是主键,而且应该是自增id。

注意

许多数据库系统只允许返回单个列,该列必须是表的自增主键。如果您要求其他列,则会在运行时引发 SlickException(除非数据库实际上支持它)。

所以写一个函数,像这样插入数据库后返回Id

def getIdAfterInsert(entity: Entity): DBIO[EntityId] = {
  (entities += entity).flatMap { _ =>
    entities.filter(_.name === entity.name).result.flatMap {
      case Some(entity) => DBIO.successful(entity.id)
      case None => DBIO.fail(new Exception("something terrible happened"))
    }
  }.transactionally
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2023-03-21
    • 2021-04-15
    • 1970-01-01
    相关资源
    最近更新 更多