2014-02-07 4 views
1

У меня есть небольшое приложение, написанное в scala, которое отправляет запрос в mysql, получает результат, а затем конвертирует его в json и отправляет на какой-то http-сервер. Я использую java jdbc и mysql-коннектор для подключения к базе данных и спрей-json для коллекции scala для преобразования json. Итак, я создаю соединение с db, выполняю запрос, а затем получаю результат с getResultSet(). Тогда я итерация через него, и скопировать результат в изменяемый карте:Как преобразовать результаты jdbc в неизменяемую коллекцию

while(result.next()) { 
    val SomeExtractor(one, two) = result 
    map.update(one, map.getOrElse(one, List()) ::: List(two)) 
} 

Это прекрасно работает, но тогда я должен конвертировать результат в непреложную карту, причина распыление JSON не может преобразовать изменяемые коллекции в формат JSON, НАСКОЛЬКО МНЕ ИЗВЕСТНО. Есть ли хороший способ конвертировать результат jdbc здесь в неизменную коллекцию, не перебирая ее на временную изменчивую карту? Может быть, можно каким-то образом использовать потоки? Я спрашиваю, потому что похоже, что для этого должен быть какой-то классный функциональный шаблон, о котором я понятия не имею.

p.s. Кстати, я не могу просто использовать Slick, потому что он не поддерживает хранимые процедуры AFAIK.

+0

Могу ли я направить вас на интерфейс JDBC Scala, который позволит вам использовать 'toList' для возврата из запросов и поддерживает хранимые процедуры? https://github.com/novus/novus-jdbc (я написал его, пока я там работал). – wheaties

+0

@wheaties Спасибо, могу ли я получить эту библиотеку из maven repo с помощью sbt? –

+0

Знаете, это могло бы помочь, если бы я сделал его доступным для скачивания, но я добрался до этого момента и никогда не публиковал его за пределами компании Maven. – wheaties

ответ

3

Короткий ответ: Вы не можете сделать значительно лучше, чем у вас. Под капотом функционального умения Scala есть код, который очень похож на ваш. Кроме того, не забывайте, что mutable Map s имеет метод toMap, который возвращает неизменяемый Map.

Долгий ответ: Вы хотите создать интерфейс кода JDBC с кодом Scala. API JDBC не предназначен для использования с функциональными языками, поэтому вам определенно потребуется какой-то изменяемый/императивный код, который поможет преодолеть разрыв. Это действительно просто вопрос о пути наименьшего сопротивления.

Если вы просто строили карту «один к одному», вы бы хорошо служили 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, но она не идеальна для вашего прецедента. Он изменен и сохраняет значения в mutable Set s, а не List s, поэтому мы будем игнорировать его пока.

В стандартной библиотеке 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 в конце.

Я думаю, что подход, который у вас уже есть, близок к оптимальному - просто верните map.toMap.

О, и я предположил, что SomeExtractor извлекает пару Int s.

+0

Спасибо вам за ответ! Но нельзя ли как-то использовать поток здесь? Чем вызов метода 'takeWhile (result.next()) для извлечения всех результатов в поток? И затем используйте его как коллекцию. Хотя я не знаю, поддерживается ли она в распылителе. –

+0

Да, я подумал о чем-то подобном. Я думаю, что вполне безопасно это делать, пока он доступен из одного потока, потому что результат будет скопирован в поток, что является неизменным. Я ошибаюсь? –

+0

Если вам нужен Stream, вам понадобится код клея. @ «GetStreamOfResults» Алексея, вероятно, лучший способ сделать это. Как только вы получите это, вы можете вызвать 'groupBy' в' Stream'. Но под обложками он все равно создаст изменчивую карту. –

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]))) 
+0

К сожалению, я не могу использовать Slick, потому что он не поддерживает хранимые процедуры. Спасибо за ваш ответ и за пример потока! –