如何在 Slick 中调用存储过程并获取返回值(使用 Scala)

2024-04-13

我正在尝试从 Slick 3.0(在 Play Framework 中)调用存储过程。我已经一遍又一遍地阅读文档,但不幸的是Typesafe 上的纯 SQL 文档 http://slick.typesafe.com/doc/3.0.0/sql.html从不显示调用存储过程。

看似非常简单的事情却导致了一条典型的晦涩难懂的 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...?
}

上面的代码会导致这个编译器错误:

[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]                                   ^

如果我使用纯硬编码的调用语句(删除所有${i.xyz}参考文献,我可以编译它...但是随后,我收到一个运行时错误报告Update statements should not return a result set.

这导致我将声明更改为常规声明sql call:

val call: DBIO[Seq[(Int)]] = sql"call app_glimpulse_invitation_pkg.n_send_invitation('xyz', 1000, 1, '[email protected] /cdn-cgi/l/email-protection', NULL, 'I', ${out})".as[(Int)]
val result: Future[Int] = db.run(call)

但这也无济于事,产生编译错误:

[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]                                              ^

我确实发现(在浏览 Slick API 时)prepareCall在会议上,但同样......没有关于如何使用这个东西的文档。

任何和所有的建议将非常感激。这对我来说已经成为一个巨大的障碍,因为我们确实需要对我们的 Postgres 存储过程进行有效调用。谢谢。


好吧,经过大量研究和审查相互冲突的文档后,我找到了答案。不幸的是,这不是我要找的:

对于返回完整表或存储的数据库函数 程序请使用纯 SQL 查询。返回的存储过程 目前不支持多个结果集。

最重要的是,Slick 不支持开箱即用的存储函数或过程,因此我们必须编写自己的函数或过程。

答案是通过抓取会话对象来退出 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()

这是一个快速演练:我创建了一个简单的容器来模拟存储过程调用。 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(?, ?, ?, ?, ?, ?, ?)}")
}

为了完整起见,我包含了 GPProcedure 枚举,以便您可以将它们放在一起。

所有这一切都交给我execute()功能。它又大又脏,闻起来像老式的 JDBC,而且我确信我会大大改进 Scala。我昨晚凌晨 3 点就完成了这个……但是它确实有效,而且效果非常好。请注意,这个特定的execute()函数返回一个List包含所有 OUT 参数...我必须编写一个单独的executeQuery()函数来处理返回一个的过程resultSet。 (不过区别是微不足道的:你只需编写一个循环来获取resultSet.next并将其全部放入List或您想要的任何其他结构)。

这是令人讨厌的 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(_ != (()))
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Slick 中调用存储过程并获取返回值(使用 Scala) 的相关文章

随机推荐

  • 单链打印C++

    我正在尝试以 1 2 3 4 etc 格式选择我的链 您可以在下面找到头文件 其中包含节点的布局 我只是对如何循环浏览列表以打印项目感到困惑 任何指导将不胜感激 set h using namespace std include
  • Go:在二进制文件中嵌入静态文件

    这可能是一个非常业余的问题 我正在尝试将静态文件嵌入到二进制文件中 即 html 我该怎么做https github com jteeuwen go bindata https github com jteeuwen go bindata
  • front() 和 begin() 之间的区别

    两者有什么区别front and begin 许多 STL 容器中出现的函数 begin http www cplusplus com reference stl list begin 返回一个可用于迭代集合的迭代器 而front http
  • OnItemCLickListener 在列表视图中不起作用

    Activity班级代码 conversationList ListView findViewById android R id list ConversationArrayAdapter conversationArrayAdapter
  • 从干草堆索引中删除对象

    我使用 django 删除一条记录 r model objects get id 1 r delete 现在我想从索引中删除记录而不重新索引 如何 我无法让remove object工作并且干草堆文档 http django haystac
  • 从 Windows 服务打印 pcl 文件的 LPR 命令不起作用(现在是托盘应用程序)

    我已经四处寻找可能的解决方案和解释一段时间了 但我找不到任何东西 以下命令正在从 Windows 服务运行 如果直接在 cmd 中使用 相同的命令也可以运行 它不会返回任何错误或与此相关的任何其他内容 System Diagnostics
  • 每 5 分钟自动运行一次 Selenium 测试

    有没有办法自动执行每五分钟运行一次的硒测试 如果您已将测试打包为可执行文件 最简单的方法可能是运行 CRON 作业或 Windows 计划任务 也就是说 Hudson 或另一个持续集成系统 几乎肯定是更好的长期策略
  • 日期格式:Android 联系人生日周年纪念日

    处理联系人生日和周年纪念日 我得到这样的详细信息和生日 12 2 2012 或 12 2 2012 或 12 02 2012 或 2 12 12 问题 所有三星手机的日期格式都相同吗 如果是 日期格式是什么 猜测不适用于所有 Android
  • sbt 中未解决的依赖关系

    运行我的sbt构建 我得到以下内容未解决的依赖关系 warn warn UNRESOLVED DEPENDENCIES warn warn com typesafe play sbt link 2 2 0 not found warn co
  • t-SQL 授予表删除和创建权限

    如何授予某些用户仅在 VB net 2005 Win 应用程序访问的 SQL 2005 数据库中删除和创建单个表的权限 有些文章建议授予表的控制权 但我无法做到这一点 如果您认为这是正确的方法 您能告诉我正确的语法吗 您不能在单个表上分配
  • 密码重置链接未重定向到网站

    我正在尝试使用 django Rest 框架在我的 React 应用程序中实现密码重置功能 我正在使用rest auth Issue 当我尝试从网站重置密码后 它会将密码重置链接发送到电子邮件 但单击该链接后 它会重定向到 DRF 默认密码
  • 如何从颤振路径中获取音频元数据?

    我有音频文件的路径 如 sdcard music ABC mp3 如何从该路径获取其他详细信息 例如专辑 艺术家 持续时间等 如果您正在使用颤动声音 https pub dev packages flutter sound 有一些实用方法可
  • 使用 Array.every 缩小数组类型的联合

    我有一个变量 其类型是不同数组类型的联合 我想将其类型缩小为该联合的单个成员 这样我就可以为每种类型运行适当的代码 使用Array every https developer mozilla org en US docs Web JavaS
  • 如何放大和缩小图像pygame并使用鼠标位置作为缩放中心

    我遇到了无法解决的 PyGame 问题 所以 我的想法是我有一张可以放大 缩小的地图 放大效果很好 但缩小后显示图片的其余部分已被删除 现在仅存在之前在窗口上可见的图像部分 这是我的代码 import pygame from pygame
  • 将上传的图像插入 Summernote 编辑器

    我的目标是将上传的图像 本地文件 插入到 Summernote 编辑器中 图片上传成功 只需插入编辑器即可 我试图控制台记录编辑器实例 但它显示未定义 如果我可以将编辑器实例传递给sendFile功能 我想这个问题将会得到解决 jsfidd
  • Rails 3.2:Heroku 推送被拒绝,未检测到 Cedar 支持的应用程序

    这里是 Rails 新手 我正在尝试将 Rails 3 1 Ruby 1 9 3 p0 应用程序部署到 Heroku 并按照 Heroku 执行了所有步骤 但我不断遇到 Heroku 推送被拒绝 未检测到 Cedar 支持的应用程序 我已经
  • 使用 2FA AD 组登录用户(DB_Owner 帐户)登录的 Azure 不允许创建架构,而普通 AD/LAN 登录用户可以创建架构

    用户 电子邮件受保护 cdn cgi l email protection是AD组的一部分 AD 组映射到数据库角色 DB OWNER 当用户 电子邮件受保护 cdn cgi l email protection尝试创建架构 我们收到以下错
  • 在 Spring 中如何从“”打印用户的名字和姓氏

    如何打印用户的名字和姓氏
  • RewriteRule 是贪婪的

    我已经找了几个小时的答案了 很抱歉 如果这个问题被问了很多次 我错过了 我基本上想重写以忽略第一个目录 路径中的第一个目录会有所不同 所以我想我可以使用正则表达式 但我的正则表达式一直与文件名匹配 RewriteRule a z 2 L 如
  • 如何在 Slick 中调用存储过程并获取返回值(使用 Scala)

    我正在尝试从 Slick 3 0 在 Play Framework 中 调用存储过程 我已经一遍又一遍地阅读文档 但不幸的是Typesafe 上的纯 SQL 文档 http slick typesafe com doc 3 0 0 sql