如何在 Scala Slick 中运行补丁/部分数据库更新?
Posted
技术标签:
【中文标题】如何在 Scala Slick 中运行补丁/部分数据库更新?【英文标题】:How do you run a patch/partial database UPDATE in Scala Slick? 【发布时间】:2015-08-31 02:47:08 【问题描述】:我们想使用 Slick (3.0.0) 运行补丁/部分 UPDATE
,以便我们只修改记录中的一些字段。究竟哪些字段将被准确更新只有在运行时才能知道。
例如,对于REST PATCH
request。
目前我们首先运行SELECT
以获取原始记录,然后运行UPDATE
,但最好在单个SQL 语句中执行此操作。
类似这样的:
def patchPerson(name: Option[String], age: Option[Int]) =
people.filter(_.name === "M Odersky")
.map(p =>
(name, age) match
case (Some(_), Some(_)) => (p.name, p.age)
case (Some(_), None) => (p.name)
case (None , Some(_)) => (p.age)
)
.update(
(name, age) match
case (Some(_), Some(_)) => (name.get, age.get)
case (Some(_), None) => (name.get)
case (None , Some(_)) => (age.get)
)
(请忽略这里丑陋的代码)
上面没有编译并出现以下错误消息:
找不到匹配的形状。 Slick 不知道如何映射给定的 类型。可能的原因: Table[T] 中的 T 与您的 * 不匹配 投影。或者您在查询中使用不受支持的类型(例如 scala 列表)。所需级别:slick.lifted.FlatShapeLevel 源类型: 对象解包类型:T 打包类型:G
还有:
方法映射的参数不足:(隐式形状: slick.lifted.Shape[_ <: slick.lifted.flatshapelevel t g>
我认为这是因为 Slick 期望元组长度和类型与 filter
和 update
函数的结果相匹配。
我们已经尝试使用 Slick heterogeneous list 类,但这似乎也期望长度和类型匹配。
有没有办法在 Slick 中编写此代码,以便我们可以通过一次数据库调用更新记录中的任意数量的字段?
【问题讨论】:
您找到解决方案了吗? 只运行原生 SQL 怎么样? 我已经添加了赏金,因为我遇到了与作者完全相同的问题。 @LukasEder 如果您不知道要修补哪些密钥,那将无法正常工作。例如。我有一个大的用户案例类要更新,我想要一个通用的 PATCH 端点,我可以简单地向它发送一个更新的字段。 我找到了一个类似问题的答案。 ***.com/a/42004236/7505973 【参考方案1】:为什么不在构造更新查询之前进行模式匹配?
def patchPerson(name: Option[String], age: Option[Int]) =
val query = people.filter(_.name === "M Odersky")
(name, age) match
case (Some(name), Some(age)) =>
query.map(p => (p.name, p.age)).update(name, age)
case (Some(name), None) =>
query.map(p => p.name).update(name)
case (None, Some(age)) =>
query.map(p => p.age).update(age)
【讨论】:
很好,谢谢。很有用 不幸的是,这不是真正可扩展的,因为n
Option
s 你得到 2^n
案例【参考方案2】:
我最好的猜测是运行plain SQL query
即使 SQL 查询有 2 个部分,关系数据库管理系统(postgresql、mysql 等)也能够在后台调整查询。
我不确定在这种情况下 Slick 是否能够优化,但在某些情况下它本身也可以optimizes the queries。
典型更新:
def updateRecord(id: Long, field1: Int) =
db.withSession
self.filter(_.id === id).map(_.field1).update(field1)
进行您的更新类型需要像您一样的更多逻辑。如果您只在运行时知道要更改哪些字段,请不要认为可以简化。但是您可以强制更新,使用记录中字段的现有值作为后备(可能导致数据库上的更新超出其应有的数量)
def updateRecord(id: Long, field1: Option[Int], field2: Option[Int]) =
db.withSession
self.filter(_.id === id).map(_.field1, _.field2).update(field1.getOrElse(existingValue1), field2.getOrElse(existingValue2))
【讨论】:
我也相信普通 SQL 目前是解决这个问题的正确方法。 Slick 可能可以按照您的建议将每个字段的一个更新优化为一个 SQL 更新作为替代方案。唯一真正的问题是如何在没有额外查询的情况下获取 existingValueX。这个问题的整个想法是避免 SELECT + UPDATE。真可惜更新函数收不到当前值。【参考方案3】:你已经得到了@pedrorijo91 和@thirstycow 所写的答案,但我会尝试解释为什么这不起作用。
我没有使用过 slick 3,但我猜测它是因为 map 函数没有返回一致的类型来运行更新。作为一个思想实验,如果您在 map 上打断电话,您认为该类型会是什么?
val partialQuery:??? = people.filter(_.name === "M Odersky")
.map(p =>
(name, age) match
case (Some(_), Some(_)) => (p.name, p.age)
case (Some(_), None) => (p.name)
case (None , Some(_)) => (p.age)
);
val fullQuery:??? = partialQuery.update
(name, age) match
case (Some(_), Some(_)) => (name.get, age.get)
case (Some(_), None) => (name.get)
case (None , Some(_)) => (age.get)
匹配器在编译时返回不同的“形状”,我猜这将恢复为任何类型。
【讨论】:
我只是想补充一下,其实还有第三种解决方法,就是写一个宏。我不打算在这里详细介绍,但它看起来根本不像你的解决方案(但可能更酷?)以上是关于如何在 Scala Slick 中运行补丁/部分数据库更新?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Play with Scala 和 Slick 从数据库中获取记录
Scala Slick 如何将 Scala 代码翻译成 JDBC?
使用 Java+Scala+Slick2D 时“无法从给定的启动配置中找到主要方法”