##1

在做一个Single Page Application,然后使用三方登陆,不维护账号信息,每次登陆拉一次最新的三方账户信息。

所以数据库操作就有个问题,没有注册,insert,老用户,update,俗称upsert。

##2

使用mysql直接写SQL语句很简单:

INSERT ... ON DUPLICATE KEY UPDATE

##3

但是这个小项目使用play-slick 1.0.1

刚开始学着用,就捣鼓出了这样一段逻辑


def insert(user: User): Future[Int] = db.run(Users += user)
  
def update(user: User): Future[Int] = {
  db.run(Users.filter(_.id === user.id.get).update(user))
}
  
def selectByQQOpenID(qqOpenID: String): Future[Option[User]] = db.run( Users.filter(_.qqOpenID === qqOpenID).result.headOption )



def createOrUpdateUserByOpenID(openID: String, userInfo: UserInfo): Future[Int] = {
	val user = userDAO.selectByQQOpenID(openID)
	user.flatMap { 
		case Some(u) => userDAO.update(u.copy(name = userInfo.name, headURL = userInfo.headURL))
		case None => userDAO.insert(User(None, userInfo.name, userInfo.headURL, openID))
	}
}

##4

感觉不漂亮,搜了一下,原来还挺多研究这个问题的……

比如:Upsert in Slick 3

原来有个内建的insertOrUpdate ,代码提示居然没让我看到……

所以对于单主键

def postReview(title: String, rating: Int): DBIO[Int] = for {
  existing <- reviews.filter(_.title === title).result.headOption
  row       = existing.map(_.copy(rating=rating)) getOrElse Review(title, rating)
  result  <- reviews.insertOrUpdate(row)
} yield result

对于联合主键

def postReview(critic: String, title: String, rating: Int): DBIO[Int] = for {
  existing <- reviews.filter(r => r.title === title && r.critic === critic).result.headOption
  row       = existing.map(_.copy(rating=rating)) getOrElse Review(critic, title, rating)
  result  <- reviews.insertOrUpdate(row)
} yield result

当然也可以纯手工

def postReview(critic: String, title: String, rating: Int)(implicit ec: ExecutionContext): DBIO[Int] = {
  for {
    rowsAffected <- reviews.filter(r => r.critic === critic && r.title === title).map(_.rating).update(rating)
    result <- rowsAffected match {
      case 0 => reviews += Review(critic, title, rating)
      case 1 => DBIO.successful(1)
      case n => DBIO.failed(new RuntimeException(s"Expected 0 or 1 change, not $n for $critic @ $title"))
    }
  } yield result
}