13

Doobie 可以select *使用案例类来方便和正确地传递参数,但我不知道如何以类似的方式使用updateand insert

例如,给定一个这样的案例类:

case class Course(
  sku: String,
  title: String,
  id: Id,
  price: Int,
  instructorid: Id,
  groupid: Id,
  shortdescription: String = "",
  transcript: String = "",
  project_home: String = "",
  repository: String = "",
  category: String = "",
  image: String = "",
  privacy: String = "",
  language: String = "",
  keywords: String = "",
  goals: String = "",
  instructionallevel: String = "",
  audience: String = "",
  studenttasks: String =  "",
  sections: String = "",
  active: Boolean = true,
  video: String = "",
  paypal_button_id: String = "",
  prerequisite_ids: String = ""
)

我可以很好地select记录。这种漂亮的语法是可能的,因为 Doobie 遍历案例类属性并通过将它们的名称与数据库记录字段Course匹配来为它们分配值:courses

    def find(id: Id): Option[Course] =
      sql"select * from courses where id = $id"
        .query[Course]
        .option
        .transact(SQLSupport.xa)
        .unsafeRunSync

但是insert,需要手动列出所有案例类属性,并与值匹配,这是可怕且容易出错的:

    /** @return saved Course with new Id */
    def save(course: Course): Course = {
      val insert: doobie.ConnectionIO[Course] = sql"""insert into courses (
          sku,
          title,
          price,
          instructorid,
          groupid,
          shortdescription,
          transcript,
          project_home,
          repository,
          category,
          image,
          privacy,
          language,
          keywords,
          goals,
          instructionallevel,
          audience,
          studenttasks,
          sections,
          active,
          video,
          paypal_button_id,
          prerequisite_ids
        ) values (
          ${ course.sku },
          ${ course.title },
          ${ course.price },
          ${ course.instructorid },
          ${ course.groupid },
          ${ course.shortdescription },
          ${ course.transcript },
          ${ course.project_home },
          ${ course.repository },
          ${ course.category },
          ${ course.image },
          ${ course.privacy },
          ${ course.language },
          ${ course.keywords },
          ${ course.goals },
          ${ course.instructionallevel },
          ${ course.audience },
          ${ course.studenttasks },
          ${ course.sections },
          ${ course.active },
          ${ course.video },
          ${ course.paypal_button_id },
          ${ course.prerequisite_ids }
        )"""
        .update
        .withUniqueGeneratedKeys("id")
      val newCourse: Course = insert.transact(SQLSupport.xa).unsafeRunSync
      newCourse
    }

update同样可怕:

    /** @return updated Course, which should be identical to the given course */
    def update(course: Course): Course = {
      val update: doobie.ConnectionIO[Course] = sql"""update courses set
          sku = ${ course.sku },
          title = ${ course.title },
          id = ${ course.id },
          price = ${ course.price },
          instructorid = ${ course.instructorid },
          groupid = ${ course.groupid },
          shortdescription = ${ course.shortdescription },
          transcript = ${ course.transcript },
          project_home = ${ course.project_home },
          repository = ${ course.repository },
          category = ${ course.category },
          image = ${ course.image },
          privacy = ${ course.privacy },
          language = ${ course.language },
          keywords = ${ course.keywords },
          goals = ${ course.goals },
          instructionallevel = ${ course.instructionallevel },
          audience = ${ course.audience },
          studenttasks = ${ course.studenttasks },
          sections = ${ course.sections },
          active = ${ course.active },
          video = ${ course.video },
          paypal_button_id = ${ course.paypal_button_id },
          prerequisite_ids = ${ course.prerequisite_ids }
        where id = ${ course.id }"""
        .update
        .withUniqueGeneratedKeys("id")
      val modifiedCourse: Course = update.transact(SQLSupport.xa).unsafeRunSync
      modifiedCourse
    }

有没有更好的办法?

4

3 回答 3

1

Doobie文档非常棒,但有时您可能会发现自己在某些情况下没有在他们的文档中直接解释。

为了直接插入一个case class对象(而不是它们的属性),您必须定义一个Write[A]告诉Doobie如何插入数据的方法。当案例类中的属性映射与数据库表中的属性映射略有不同时,使用此方法。

想象以下案例类:

case class Course (id: UUID, name: String, year: Int)

在这种情况下,我们需要Write[Course]为 doobie 定义一个,即:

// Scala 3:
given Write[Course] = Write[(UUID, String, Int)].contramap(c => (c.id, c.name, c.year))

// Scala 2:
implicit val writer : Write[Course] = Write[(UUID, String, Int)].contramap(c => (c.id, c.name, c.year))

所以现在,您可以运行您的Update并且Doobie将知道如何映射您的列:

def insertCourse(course: Course): Update0 =
    sql"""INSERT INTO courses (id, name, year) VALUES ($course)""".update

此外,您可能需要这些导入:

import doobie.implicits.*
import doobie.implicits.javasql.*
import doobie.postgres.implicits.*
import doobie.*

如果您的case class属性及其类型与数据库表中指定的属性完全匹配,则无需手动指定,Writer[Course]因为 Doobie 会自动为您派生 [ 1 ],这应该适合您:

case class Course (id: UUID, name: String, year: Int)

def insertCourse(course: Course): Update0 =
  sql"""INSERT INTO courses (id, name, year) VALUES ($course)""".update

感谢我的合作伙伴 YC 帮助我解决了这个问题!

于 2022-02-24T10:20:14.113 回答
0

如果您使用的是 Postgres,您可以查看 Rob Norris 的另一个库 - skunk

它允许您编写自定义编解码器:

  case class City(id: Int, name: String, code: String, district: String, pop: Int)

  val city: Codec[City] =
    (int4 ~ varchar ~ bpchar(3) ~ varchar ~ int4).gimap[City]

  val insertCity: Command[City] =
    sql"""
         INSERT INTO city
         VALUES ($city)
       """.command

检查示例

于 2019-12-29T09:54:28.457 回答
0

Doobie 具有来自getquill.io的 Quill 集成,它允许您使用案例类对 sql DML 建模 https://github.com/polyvariant/doobie-quill

于 2021-07-08T14:48:33.903 回答