我正在设计一个2层架构的API接口。它从URL获取字符串参数fieldName并返回JSON字符串。 fieldName是指我的数据库表中的一个字段。您可以将其签名视为:
def controller(fieldName: String): String
在控制器中,我想在我的数据访问层中调用一个方法来执行以下查询:
SELECT fieldName, SUM(salary) FROM Employee GROUP BY fieldName
因为字段的类型不同,查询结果的类型也会不同。此方法由泛型类型参数T参数化,该参数对应于名称为fieldName的字段的类型。
def getTotalSalaryByField[T](fieldName: String): Map[T, Long]
在运行时给定一个特定的fieldName,如何调用此方法为其提供正确的类型?
我不想写很多if-else或模式匹配语句来选择类型。它看起来像这样:
fieldName match {
case "age" => serializeToJson(getTotalSalaryByField[Int]("age"))
case "name" => serializeToJson(getTotalSalaryByField[String]("name"))
...
// 100 more for 100 more fields
}
这太丑了。如果我用Python编写它,它只需要一行:
json.dumps(getTotalSalaryByField(fieldName))
Scala不适合休息后端编程吗?因为这似乎是人们会遇到的常见模式,静态类型会妨碍。我希望看到一些关于如何以scala-ish方式处理整个问题的建议,即使这意味着重新构建,重写DAL和控制器。
修改: @drexin,DAL方法的实际签名是
def myDAOMethod[T](tf: Option[TimeFilter], cf: CrossFilter)
(implicit attr: XFAttribute[T]): Map[T, Long]
T是fieldName的类型。 y是y的类型。如您所见,我需要根据url参数中的fieldName选择一个T类型。
修改: 添加了一些代码并使用例明确
答案 0 :(得分:2)
在编译时和运行时知道变量的类型之间存在差异。
如果在编译时不知道fieldName
(即它是参数),并且列的类型变化fieldName
,那么您将无法在编译时指定方法的返回类型。
您将需要使用返回AnyRef
的DAO方法,而不是使用返回T
的方法来指定编译时指定的类型T
。
数据库访问库可以返回值而无需知道它们的类型,您的代码也需要这样做。
您希望使用DAO方法,该方法采用类型参数T
:
def myDAOMethod[T](tf: Option[TimeFilter], cf: CrossFilter)
(implicit attr: XFAttribute[T]): Map[T, Long]
...但是如您所述,您事先并不知道类型T
,因此此方法不适用。 (T
用于将数据库列数据转换为String或Int)。
你的DAO应该提供这种方法的无类型版本,更像是:
def doSelect(tf: Option[TimeFilter], cf: CrossFilter): Map[AnyRef, Long]
您使用的是什么数据库访问库?
答案 1 :(得分:2)
如果您使用Play框架,那么我建议您使用Play JSON API。
https://www.playframework.com/documentation/2.1.1/ScalaJson
而不是
def getTotalSalaryByField[T](fieldName: String): Map[T, Long] = ???
你可以写
def getTotalSalaryByField(fieldName: String): Map[JsValue, Long] = ???
因为JsNumber
,JsString
,JsNull
等所有类型都是从通用JSON特征JsValue
继承的。
用Json.toJson()
或强>
def getTotalSalaryByField(fieldName: String): JsObject = ???
答案 2 :(得分:1)
我认为你有三种选择:
第三个选项可能正是您所寻找的,但它基于实验性scala功能(即宏)。我想它将要做的是在编译阶段连接到您的数据库并检查模式。如果您使用一些单独的sql文件生成模式,那么它应该更容易。然后宏将生成所有样板的硬编码。
您可能无法找到确切需要的工作示例,但有一个可用作灵感的RFC文件:https://github.com/travisbrown/type-provider-examples
答案 3 :(得分:0)
我不是100%确定我理解你的问题,如果我完全回答其他问题,请道歉。
您需要Scala才能根据预期的返回类型猜测字段的类型。也就是说,在以下代码中:
val result : Map[String, Long] = myDAOMethod(tf, cf)
您希望Scala正确推断,因为您需要Map[String, Long]
,所以x
变量的类型为T
。
在我看来,你已经拥有了所需的一切。我不知道XFAttribute[T]
是什么,但我怀疑它允许您将结果集中的条目转换为T
类型的实例。
除此之外,您已经将其声明为隐含参数。
如果范围内有隐式XFAttribute[String]
,则前面的代码应该编译,运行并且是类型安全的。
作为一项小改进,我改变了方法的签名以使用上下文界限,但这主要是品味问题:
// Declare the implicit "parsers"
implicit val IntAttribute: XFAttribute[Int] = ???
implicit val StringAttribute: XFAttribute[String] = ???
// Empty implementation, fill it with your actual code.
def myDAOMethod[T: XFAttribute](tf: Option[TimeFilter], cf: CrossFilter): Map[T, Long] = ???
// Scala will look for an implicit XFAttribute[Int] in scope, and find IntAttribute.
val ages: Map[Int, Long] = myDAOMethod(tf, cf)
// Scala will look for an implicit XFAttribute[String] in scope, and find StringAttribute.
val names: Map[String, Long] = myDAOMethod(tf, cf)
我不确定您的问题是否意味着您还希望将字符串"age"
强烈绑定到类型Int
。当然,这也是可能的,但它完全是另一个答案,我不想用不必要的漫无边际污染这个问题。