Zac*_*Zac 7 postgresql stored-procedures scala playframework slick
我正在尝试从Slick 3.0(在Play Framework中)调用存储过程.我一直在翻阅文档,但不幸的是,Typesafe中的普通SQL文档从未显示调用存储过程.
看起来非常简单的是导致一个通常模糊的Scala错误消息:
val f = Try {
val call: DBIO[Int] = sqlu"?=call app_glimpulse_invitation_pkg.n_send_invitation(${i.token}, ${i.recipientAccountId.getOrElse(None)}, ${i.email}, ${i.phoneNumber}, ${requestType})"
val result: Future[Int] = db.run(call)
val r = Await.result(result, Duration.Inf) // should only return one; use .seq.count(_.id != null)) to validate
val z = result.value.get.get // should return the stored procedure return value...?
}
Run Code Online (Sandbox Code Playgroud)
上面的代码导致此编译器错误:
[error] /Users/zbeckman/Projects/Glimpulse/Server-2/project/glimpulse-server/app/controllers/GPInviteService/GPInviteService.scala:120: could not find implicit value for parameter e: slick.jdbc.SetParameter[Product with Serializable]
[error] val call: DBIO[Int] = sqlu"?=call app_glimpulse_invitation_pkg.n_send_invitation(${i.token}, ${i.recipientAccountId.getOrElse(None)}, ${i.email}, ${i.phoneNumber}, ${requestType})"
[error] ^
Run Code Online (Sandbox Code Playgroud)
如果我使用纯硬编码的调用语句(删除所有${i.xyz}引用,我可以让它编译...但是,然后,我得到一个运行时错误报告Update statements should not return a result set.
这导致我将声明改为普通sql电话:
val call: DBIO[Seq[(Int)]] = sql"call app_glimpulse_invitation_pkg.n_send_invitation('xyz', 1000, 1, 'me@here.com', NULL, 'I', ${out})".as[(Int)]
val result: Future[Int] = db.run(call)
Run Code Online (Sandbox Code Playgroud)
但这也无处可去,产生编译错误:
[error] /Users/zbeckman/Projects/Glimpulse/Server-2/project/glimpulse-server/app/controllers/GPInviteService/GPInviteService.scala:126: type mismatch;
[error] found : slick.driver.PostgresDriver.api.DBIO[Seq[Int]]
[error] (which expands to) slick.dbio.DBIOAction[Seq[Int],slick.dbio.NoStream,slick.dbio.Effect.All]
[error] required: slick.dbio.DBIOAction[Int,slick.dbio.NoStream,Nothing]
[error] val result: Future[Int] = db.run(call)
[error] ^
Run Code Online (Sandbox Code Playgroud)
我确实找到了(在浏览Slick API的同时)a prepareCall会话,但是再次......没有关于如何使用这个东西的文档.
任何和所有的建议将非常感激.这对我来说已经成为一个巨大的障碍,因为我们真的需要打电话给我们的Postgres存储过程.谢谢.
好吧,在对相互矛盾的文档进行了大量研究和审查之后,我找到了答案。不幸的是,这不是我要找的:
对于返回完整表或存储过程的数据库函数,请使用普通 SQL 查询。当前不支持返回多个结果集的存储过程。
最重要的是,Slick 不支持开箱即用的存储函数或过程,因此我们必须自己编写。
答案是通过抓取 session 对象从 Slick 中下拉,然后使用标准的 JDBC 来管理过程调用。对于那些熟悉 JDBC 的人来说,这不是一种乐趣……但是,幸运的是,使用 Scala,我们可以使用模式匹配来做一些非常好的技巧,使工作更轻松。
对我来说,第一步是组装一个干净的外部 API。这就是它最终的样子:
val db = Database.forDataSource(DB.getDataSource)
var response: Option[GPInviteResponse] = None
db.withSession {
implicit session => {
val parameters = GPProcedureParameterSet(
GPOut(Types.INTEGER) ::
GPIn(Option(i.token), Types.VARCHAR) ::
GPIn(recipientAccountId, Types.INTEGER) ::
GPIn(Option(contactType), Types.INTEGER) ::
GPIn(contactValue, Types.VARCHAR) ::
GPIn(None, Types.INTEGER) ::
GPIn(Option(requestType), Types.CHAR) ::
GPOut(Types.INTEGER) ::
Nil
)
val result = execute(session.conn, GPProcedure.SendInvitation, parameters)
val rc = result.head.asInstanceOf[Int]
Logger(s"FUNC return code: $rc")
response = rc match {
case 0 => Option(GPInviteResponse(true, None, None))
case _ => Option(GPInviteResponse(false, None, Option(GPError.errorForCode(rc))))
}
}
}
db.close()
Run Code Online (Sandbox Code Playgroud)
这是一个快速演练:我创建了一个简单的容器来为存储过程调用建模。GPProcedureParameterSet 可以包含 GPIn、GPOut 或 GPInOut 实例的列表。其中每一个都将一个值映射到一个 JDBC 类型。容器看起来像这样:
case class GPOut(parameterType: Int) extends GPProcedureParameter
object GPOut
case class GPIn(value: Option[Any], parameterType: Int) extends GPProcedureParameter
object GPIn
case class GPInOut(value: Option[Any], parameterType: Int) extends GPProcedureParameter
object GPInOut
case class GPProcedureParameterSet(parameters: List[GPProcedureParameter])
object GPProcedureParameterSet
object GPProcedure extends Enumeration {
type GPProcedure = Value
val SendInvitation = Value("{?=call app_glimpulse_invitation_pkg.n_send_invitation(?, ?, ?, ?, ?, ?, ?)}")
}
Run Code Online (Sandbox Code Playgroud)
为了完整起见,我包括了 GPProcedure 枚举,以便您可以将它们放在一起。
所有这些都交给了我的execute()职能部门。它又大又脏,闻起来像老式的 JDBC,而且我确信我会对 Scala 进行很多改进。我真的是在昨晚凌晨 3 点完成的……但它确实有效,而且效果非常好。请注意,此特定execute()函数返回List包含所有 OUT 参数的 ...... 我必须编写一个单独的executeQuery()函数来处理返回resultSet. (不过,区别很简单:您只需编写一个循环,将 a 抓取resultSet.next并将其全部塞入 aList或您想要的任何其他结构中)。
这是令人讨厌的 Scala<->JDBC 映射execute()函数:
def execute(connection: Connection, procedure: GPProcedure, ps: GPProcedureParameterSet) = {
val cs = connection.prepareCall(procedure.toString)
var index = 0
for (parameter <- ps.parameters) {
index = index + 1
parameter match {
// Handle any IN (or INOUT) types: If the optional value is None, set it to NULL, otherwise, map it according to
// the actual object value and type encoding:
case p: GPOut => cs.registerOutParameter(index, p.parameterType)
case GPIn(None, t) => cs.setNull(index, t)
case GPIn(v: Some[_], Types.NUMERIC | Types.DECIMAL) => cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal])
case GPIn(v: Some[_], Types.BIGINT) => cs.setLong(index, v.get.asInstanceOf[Long])
case GPIn(v: Some[_], Types.INTEGER) => cs.setInt(index, v.get.asInstanceOf[Int])
case GPIn(v: Some[_], Types.VARCHAR | Types.LONGVARCHAR) => cs.setString(index, v.get.asInstanceOf[String])
case GPIn(v: Some[_], Types.CHAR) => cs.setString(index, v.get.asInstanceOf[String].head.toString)
case GPInOut(None, t) => cs.setNull(index, t)
// Now handle all of the OUT (or INOUT) parameters, these we just need to set the return value type:
case GPInOut(v: Some[_], Types.NUMERIC) => {
cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal]); cs.registerOutParameter(index, Types.NUMERIC)
}
case GPInOut(v: Some[_], Types.DECIMAL) => {
cs.setBigDecimal(index, v.get.asInstanceOf[java.math.BigDecimal]); cs.registerOutParameter(index, Types.DECIMAL)
}
case GPInOut(v: Some[_], Types.BIGINT) => {
cs.setLong(index, v.get.asInstanceOf[Long]); cs.registerOutParameter(index, Types.BIGINT)
}
case GPInOut(v: Some[_], Types.INTEGER) => {
cs.setInt(index, v.get.asInstanceOf[Int]); cs.registerOutParameter(index, Types.INTEGER)
}
case GPInOut(v: Some[_], Types.VARCHAR) => {
cs.setString(index, v.get.asInstanceOf[String]); cs.registerOutParameter(index, Types.VARCHAR)
}
case GPInOut(v: Some[_], Types.LONGVARCHAR) => {
cs.setString(index, v.get.asInstanceOf[String]); cs.registerOutParameter(index, Types.LONGVARCHAR)
}
case GPInOut(v: Some[_], Types.CHAR) => {
cs.setString(index, v.get.asInstanceOf[String].head.toString); cs.registerOutParameter(index, Types.CHAR)
}
case _ => { Logger(s"Failed to match GPProcedureParameter in executeFunction (IN): index $index (${parameter.toString})") }
}
}
cs.execute()
// Now, step through each of the parameters, and get the corresponding result from the execute statement. If there is
// no result for the specified column (index), we'll basically end up getting a "nothing" back, which we strip out.
index = 0
val results: List[Any] = for (parameter <- ps.parameters) yield {
index = index + 1
parameter match {
case GPOut(Types.NUMERIC) | GPOut(Types.DECIMAL) => cs.getBigDecimal(index)
case GPOut(Types.BIGINT) => cs.getLong(index)
case GPOut(Types.INTEGER) => cs.getInt(index)
case GPOut(Types.VARCHAR | Types.LONGVARCHAR | Types.CHAR) => cs.getString(index)
case GPInOut(v: Some[_], Types.NUMERIC | Types.DECIMAL) => cs.getInt(index)
case GPInOut(v: Some[_], Types.BIGINT) => cs.getLong(index)
case GPInOut(v: Some[_], Types.INTEGER) => cs.getInt(index)
case GPInOut(v: Some[_], Types.VARCHAR | Types.LONGVARCHAR | Types.CHAR) => cs.getString(index)
case _ => {
Logger(s"Failed to match GPProcedureParameter in executeFunction (OUT): index $index (${parameter.toString})")
}
}
}
cs.close()
// Return the function return parameters (there should always be one, the caller will get a List with as many return
// parameters as we receive):
results.filter(_ != (()))
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3534 次 |
| 最近记录: |