我有一个用scala编写的小应用程序,它向mysql发送请求,接收结果,然后将其转换为json并发送到某个http服务器。我使用java jdbc和mysql连接器连接到数据库,并使用spray-json进行scala集合到json转换。因此,我创建了与db的连接,执行查询,然后使用getResultSet()
获得结果。然后我遍历它,并将结果复制到一个可变的地图:
while(result.next()) {
val SomeExtractor(one, two) = result
map.update(one, map.getOrElse(one, List()) ::: List(two))
}
这个工作正常,但后来我必须将结果转换为不可变映射,导致spray-json无法将可变集合转换为json,AFAIK。有没有一种很好的方法将jdbc结果转换为不可变集合而不将其复制到临时可变映射?也许有可能以某种方式使用流?我问,因为它看起来似乎必须有一些很酷的功能模式,我不知道。
P.S。顺便说一句,我不能只使用Slick,因为它不支持存储过程,AFAIK。
答案 0 :(得分:4)
类似Slick之类的东西也可以满足您的需求。
或者,这是我曾经写过的代码。它为您提供了一个JSON文档和元信息流,它基于Lift JSON库,但您可以轻松地将其更改为其他JSON实现。它运作得很好。
case class ColumnMeta(index: Int, label: String, datatype: String)
def runQuery(dbConnection: Connection, query: String): (List[ColumnMeta], Stream[JObject]) = {
val rs = dbConnection.prepareStatement(query).executeQuery
implicit val cols = getColumnMeta(rs.getMetaData)
(cols, getStreamOfResults(rs))
}
/**
* Returns a list of columns for specified ResultSet which describes column properties we are interested in.
*/
def getColumnMeta(rsMeta: ResultSetMetaData): List[ColumnMeta] =
(for {
idx <- (1 to rsMeta.getColumnCount)
colName = rsMeta.getColumnLabel(idx).toLowerCase
colType = rsMeta.getColumnClassName(idx)
} yield ColumnMeta(idx, colName, colType)).toList
/**
* Creates a stream of results on top of a ResultSet.
*/
def getStreamOfResults(rs: ResultSet)(implicit cols: List[ColumnMeta]): Stream[JObject] =
new Iterator[JObject] {
def hasNext = rs.next
def next() = rowToObj(rs)
}.toStream
/**
* Given a row from a ResultSet produces a JSON document.
*/
def rowToObj(rs: ResultSet)(implicit cols: List[ColumnMeta]): JObject = {
val fields = for {
ColumnMeta(index, label, datatype) <- cols
clazz = Class.forName(datatype)
value = columnValueGetter(datatype, index, rs)
} yield (label -> value)
JObject(fields map { case (n, v) => JField(n, v) })
}
/**
* Takes a fully qualified Java type as String and returns one of the subtypes of JValue by fetching a value
* from result set and converting it to proper type.
* It supports only the most common types and everything else that does not match this conversion is converted
* to String automatically. If you see that you results should contain more specific type instead of String
* add conversion cases to {{{resultsetGetters}}} map.
*/
def columnValueGetter(datatype: String, columnIdx: Int, rs: ResultSet): JValue = {
val obj = rs.getObject(columnIdx)
if (obj == null)
JNull
else {
val converter = resultsetGetters getOrElse (datatype, (obj: Object) => JString(obj.toString))
converter(obj)
}
}
val resultsetGetters: Map[String, Object => JValue] = Map(
"java.lang.Integer" -> ((obj: Object) => JInt(obj.asInstanceOf[Int])),
"java.lang.Long" -> ((obj: Object) => JInt(obj.asInstanceOf[Long])),
"java.lang.Double" -> ((obj: Object) => JDouble(obj.asInstanceOf[Double])),
"java.lang.Float" -> ((obj: Object) => JDouble(obj.asInstanceOf[Float])),
"java.lang.Boolean" -> ((obj: Object) => JBool(obj.asInstanceOf[Boolean])),
"java.sql.Clob" -> ((obj: Object) => {
val clob = obj.asInstanceOf[Clob]
JString(clob.getSubString(1, clob.length.toInt))
}),
"java.lang.String" -> ((obj: Object) => JString(obj.asInstanceOf[String])))
答案 1 :(得分:3)
简短回答:你做得比你所做的要好得多。在Scala的引擎盖下,功能聪明的代码看起来很像你的代码。另外,不要忘记mutable Map
有一个toMap
方法,它返回一个不可变的Map
。
长答案:您正在寻求使用Scala代码创建JDBC代码接口。 JDBC的API并非设计用于函数式语言,因此您肯定需要一些可变/命令式代码来帮助缩小差距。这实际上只是阻力最小的问题。
如果您只是制作一对一地图,那么MapBuilder
可以为您提供良好的服务。 Scala包含大多数数据结构的Builder
类,它们使用临时的,私有的,可变的结构来尽可能高效地构建不可变结构。代码看起来像:
val builder = Map.newBuilder[Int, Int]
while(result.next()) {
val SomeExtractor(one, two) = result
builder += one -> two
}
return builder.result
然而,你真的在构建一个MultiMap--一个从键到多个值的映射。 Scala在其标准库中确实具有MultiMap
特性,但它并不适合您的用例。它是可变的,并且将值存储在可变Set
s而不是List
中,因此我们暂时忽略它。
Scala的标准库在groupBy
特征上确实有一个Traversable
方法,它可以或多或少地提供您正在寻找的方法。我们有ResultSet
而不是Traversable
,但原则上我们可以编写一些粘合代码将ResultSet
包裹在Traversable
中,并利用现有的码。如下所示:
// strm has side effects, caused by rs.next - only ever call it once, and re-use result if needed.
def strm: Stream[(Int, Int)] = if (rs.next) SomeExtractor.unapply(rs).get #:: strm else Stream.empty
return strm.groupBy(_._1)
这样可行,但我们对副作用有一个可怕的警告,我们实际上并没有获得任何表现。如果您查看Traversable.groupBy
(see code on GitHub)的源代码,它实际上与您的内容完全相同 - 使用我们的数据构建可变Map
,然后将其转换为不可变Map
1}}在最后。
我认为您已经获得的方法接近最优 - 只需返回map.toMap
。
哦,我假设SomeExtractor提取了一对Int
。