有没有办法可以在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
}
答案 0 :(得分:35)
更新了Slick 2.1中的本机upsert / merge支持
您必须将纯SQL嵌入与数据库本机MERGE语句一起使用。所有模拟此陈述的试验都很可能导致不正确的结果。
当您模拟upsert / merge语句时,Slick将必须使用多个语句来达到该目标(例如,先选择一个select,然后选择insert或update语句)。在SQL事务中运行多个语句时,它们通常不具有与单个语句相同的隔离级别。使用不同的隔离级别,您将在大量并发情况下遇到奇怪的效果。因此,在测试过程中一切都会正常工作,并且在生产过程中会出现奇怪的效果。
在同一事务中的两个语句之间运行一个语句时,数据库通常具有更强的隔离级别。而一个运行语句不会受到并行运行的其他语句的影响。数据库将锁定语句所触及的所有内容,或者它将检测运行语句之间的相互关系,并在必要时自动重新启动有问题的语句。当执行同一事务中的下一个语句时,此保护级别不成立。
所以下面的场景可能(并且会!)发生:
user.firstOption
后面的select语句找不到当前用户的数据库行。公平地说,隔离级别"serializable"不会发生这种情况。但是这种隔离级别带有巨大的性能,在生产中很少使用。此外,可序列化将需要您的应用程序的一些帮助:数据库管理系统通常不会真正序列化所有事务。但它会检测违反可序列化的重新发送的行为,并且只是中止有问题的交易。因此,您的应用程序必须准备好重新运行由DBMS中止(随机)的事务。
如果您依赖于违规发生,请设计您的应用程序,使其自动重新运行相关交易而不会打扰用户。这类似于隔离级别“可序列化”的要求。
对此方案使用纯SQL或准备生产中的令人不快的意外。三思而后行可能的并发问题。
使用Slick 2.1.0,现在可以对MERGE语句进行本机支持(请参阅release notes:“插入或更新支持,尽可能使用本机数据库功能”)。
代码将如下所示(取自Slick test cases):
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)
}
答案 1 :(得分:1)
Apparently这不是(还没?)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))
}