有没有办法可以在Slick中巧妙地进行upsert操作?以下工作,但太模糊/冗长,我需要明确说明应更新的字段:
val id = 1
val now = new Timestamp(System.currentTimeMillis)
val q = for { u <- Users if u.id === id } yield u.lastSeen
q.update(now) match {
case 0 => Users.insert((id, now, now))
case _ => Unit
}
Run Code Online (Sandbox Code Playgroud)
ste*_*hke 36
更新了Slick 2.1中的本机upsert/merge支持
您必须将纯SQL嵌入与数据库本机MERGE语句一起使用.所有模拟此陈述的试验都很可能导致不正确的结果.
当您模拟upsert/merge语句时,Slick将不得不使用多个语句来达到该目标(例如,先选择一个select,然后选择insert或update语句).在SQL事务中运行多个语句时,它们通常不具有与单个语句相同的隔离级别.使用不同的隔离级别,您将在大量并发情况下遇到奇怪的效果.所以在测试过程中一切都会正常工作,并且在生产中会出现奇怪的效果.
在同一事务中的两个语句之间运行一个语句时,数据库通常具有更强的隔离级别.而一个运行语句不会受到并行运行的其他语句的影响.数据库将锁定语句所触及的所有内容,或者它将检测运行语句之间的相互关系,并在必要时自动重新启动有问题的语句.执行同一事务中的下一个语句时,此保护级别不成立.
所以下面的场景可能会(也会!)发生:
user.firstOption
找不到当前用户的数据库行.公平地说,隔离级别"可序列化"不会发生这种情况.但是这种隔离级别带来了巨大的性能损失,很少用于生产.此外,可序列化将需要您的应用程序的一些帮助:数据库管理系统通常不会真正序列化所有事务.但它会检测违反可序列化的重新发送的行为,并且只是中止有问题的交易.因此,您的应用程序必须准备好重新运行DBMS中止(随机)的事务.
如果您依赖于违规发生,请设计您的应用程序,使其自动重新运行相关事务而不会打扰用户.这类似于隔离级别"可序列化"的要求.
对此方案使用纯SQL或准备生产中的令人不快的意外.三思而后行并发问题.
使用Slick 2.1.0,现在可以对MERGE语句进行本机支持(请参阅发行说明:"插入或更新支持,尽可能使用本机数据库功能").
代码看起来像这样(取自Slick测试用例):
def testInsertOrUpdatePlain {
class T(tag: Tag) extends Table[(Int, String)](tag, "t_merge") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = (id, name)
def ins = (id, name)
}
val ts = TableQuery[T]
ts.ddl.create
ts ++= Seq((1, "a"), (2, "b")) // Inserts (1,a) and (2,b)
assertEquals(1, ts.insertOrUpdate((3, "c"))) // Inserts (3,c)
assertEquals(1, ts.insertOrUpdate((1, "d"))) // Updates (1,a) to (1,d)
assertEquals(Seq((1, "d"), (2, "b"), (3, "c")), ts.sortBy(_.id).run)
}
Run Code Online (Sandbox Code Playgroud)
显然这还没有在 Slick 中。
不过,您可以尝试firstOption
一些更惯用的东西:
val id = 1
val now = new Timestamp(System.currentTimeMillis)
val user = Users.filter(_.id is id)
user.firstOption match {
case Some((_, created, _)) => user.update((id, created, now))
case None => Users.insert((id, now, now))
}
Run Code Online (Sandbox Code Playgroud)