以优雅的方式传递Slick 2.0隐式会话

时间:2014-04-30 11:50:07

标签: scala session playframework slick implicit

我是Slick和Scala的新手。首先看看我的示例表,其中包含案例类映射和查询SuitsManager的帮助器。现在,SuitsManager的方法被Play调用! DBAction中的控制器(我正在使用play-slick 0.6.0.1)。

package models

import play.api.db.slick._
import play.api.db.slick.Config.driver.simple._

import scala.collection.immutable.HashMap
import scala.slick.jdbc.JdbcBackend

case class Suit(id:Option[Long],
                complainant: String,
                defender: String,
                litigation: Long,
                litigationValue: BigDecimal,
                status: Long)

class Suits(tag: Tag) extends Table[Suit](tag, "SUITS") {

  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def complainant = column[String]("complainant")
  def defender = column[String]("defender")
  def litigation = column[Long]("litigation")
  def litigationValue = column[BigDecimal]("litigationValue")
  def status = column[Long]("status")

  def * = (id.?, complainant, defender, litigation, litigationValue, status) <> (Suit.tupled, Suit.unapply)

}

object SuitsManager {
  val suits = TableQuery[Suits]

  def list(offset: Int, limit: Int, filter: String = "%")(implicit session: JdbcBackend#Session) = {
    suits.filter(_.defender like "%").drop(offset).take(limit).list
  }

  def count(offset: Int, limit: Int, filter: String = "%") : Long = {    
    suits.filter(_.defender like "%").drop(offset).take(limit).length.run
  }
}

现在看看SuitsController中第一个编译的2个方法,因为它声明了隐式会话参数。第二个给出了编译错误:

could not find implicit value for parameter session: play.api.db.slick.Config.driver.Backend#Session

因此,为查询创建辅助对象似乎不是很优雅。如果没有声明隐式会话参数,还有其他方法吗?也许使用导入?我的第二个问题:会话参数类型JdbcBackend#Session是否正确?为什么不只是Session?

2 个答案:

答案 0 :(得分:7)

第一个问题: 无法以某种方式避免绕过Session

您传递的隐式Session不是您的应用程序的某些全局信息。 Session对象表示您与数据库的当前打开会话。使用Play Slick,当向DBAction发出请求时,将为您打开此数据库会话。

这意味着您的Session仅可用,并且严格依赖于HTTP请求。事实上,您会发现它是implicit request =>中每个DBAction中注明的字段:

val someAction = DBAction { implicit request =>      // DBAction opens database session, and puts it in request.dbSession
   // Database session for this request is implicitly available on
   // the scope here and therefore may be passed to other methods implicitly
}  // Database session is closed

因此,每个请求都有一个新的,不同的数据库会话。此外,每个数据库交互都需要数据库会话。因此,执行某些查询的每个方法都需要提供当前用于处理特定请求的Session

通常使用implicits的原因是因为它提供了传递此会话的最干净的代码。

// With implicits
def helperMethod1(param: Any)(implicit s: Session) = someQuery1.list // Session is passed implicitly 
def helperMethod2(param: Any)(implicit s: Session) = someQuery2.exists.run // Session is passed implicitly

def action = DBAction { implicit request =>
   // Stuff
   helperMethod1(param1) // request.dbSession is passed implicitly
   // Stuff
   helperMethod2(param2) // request.dbSession is passed implicitly
   // Stuff
}

与重复性更强

// Without implicits
def helperMethod1(param: Any, s: Session) = someQuery.list(s)  // Must pass Session explicitly
def helperMethod2(param: Any, s: Session) = someQuery.exists.run(s)

def action = DBAction { implicit request =>
   val session = request.dbSession
   // Stuff
   helperMethod1(param1, session) // Have to repeat session for every DB call
   // Stuff
   helperMethod2(param2, session)
   // Stuff
}

@cvogt提到的Slick文档问题中的示例不是解决问题的好方法,因为它不会减少在这种情况下需要传递的参数数量。

第二个问题: 通常,SessionJdbcBackend#Session的所谓类型别名。这意味着type Session = JdbcBackend#Session,即它们完全相同。您可以安全地在任何代码中使用Session,但不幸的是,Play控制器代码。

原因是Play控制器还定义了类型Session。 Play的Session代表当前的HTTP会话,基于用户设置的cookie。不幸的是,这个命名与Slick的Session冲突。

要解决此问题,您可以为导入设置别名:import scala.slick.driver.JdbcProfile.simple.{Session => SlickSession}

答案 1 :(得分:1)

如果重复困扰你,你可以把它变成一个帮助类,并将会话作为类的参数。这里解释了:http://slick.typesafe.com/doc/2.0.1/connection.html#passing-sessions-around

您建议的导入解决方案也存在,但它的类型安全性较低。请参阅:http://slick.typesafe.com/doc/2.0.1/connection.html#dynamically-scoped-sessions

会话是Slick驱动程序蛋糕中的路径依赖类型(如蛋糕模式),这就是需要通过#运算符

访问它的原因。