2012-03-09 17 views
33

Khi tôi truy vấn một cơ sở dữ liệu và nhận được một trở về (chỉ-chỉ đọc) ResultSet, ResultSet hoạt động như một danh sách các hàng cơ sở dữ liệu.Xử lý một tập kết quả SQL như một dòng Scala

Tôi đang cố gắng tìm một số cách để xử lý ResultSet này như Scala Stream. Điều này sẽ cho phép các hoạt động như filter, map, v.v. trong khi không tiêu tốn một lượng lớn RAM.

tôi thực hiện một phương pháp đuôi-đệ quy để trích xuất các mặt hàng cá nhân, nhưng điều này đòi hỏi tất cả các mục có trong bộ nhớ cùng một lúc, một vấn đề nếu ResultSet là rất lớn:

// Iterate through the result set and gather all of the String values into a list 
// then return that list 
@tailrec 
def loop(resultSet: ResultSet, 
     accumulator: List[String] = List()): List[String] = { 
    if (!resultSet.next) accumulator.reverse 
    else { 
    val value = resultSet.getString(1) 
    loop(resultSet, value +: accumulator) 
    } 
} 
+0

Bạn có thể sử dụng Iterable thay vì Luồng để làm những gì bạn muốn không? –

+3

Ngoài ra, luồng sẽ giữ lại các giá trị trong bộ nhớ, vì vậy bạn sẽ không thực sự lưu bộ nhớ vào thời điểm bạn đến cuối danh sách. –

+0

Tôi nghĩ rằng không có một cờ/tùy chọn jdbc làm cho chính jdbc truyền các kết quả, bạn vẫn có một bản sao đầy đủ của dữ liệu trong bộ nhớ, được xây dựng bởi api jdbc của bạn. – matanster

Trả lời

61

tôi didn' t kiểm tra nó, nhưng tại sao nó sẽ không hoạt động?

new Iterator[String] { 
    def hasNext = resultSet.next() 
    def next() = resultSet.getString(1) 
}.toStream 
+0

Điều đó có vẻ hoàn hảo. Tôi sẽ kiểm tra nó ngay sau khi tôi thiết lập cơ sở dữ liệu của mình. Tôi thậm chí không nghĩ rằng tôi cần phải chuyển đổi nó thành một 'Stream'. Tôi có thể áp dụng 'map',' filter', vv trực tiếp vào nó. – Ralph

+1

Đã thử và nó hoạt động như một sự quyến rũ! Cảm ơn. – Ralph

+1

Tôi muốn cung cấp cho bạn một cuộc bỏ phiếu thứ hai.Tôi đã thêm đoạn mã này vào thư viện đoạn trích Scala của tôi. Nó nhanh chóng trở thành một trong những mục yêu thích của tôi. – Ralph

3

tôi cần một cái gì đó tương tự. Dựa trên câu trả lời rất mát mẻ elbowich, tôi quấn nó một chút, và thay vì chuỗi, tôi trả về kết quả (để bạn có thể nhận được bất kỳ cột)

def resultSetItr(resultSet: ResultSet): Stream[ResultSet] = { 
    new Iterator[ResultSet] { 
     def hasNext = resultSet.next() 
     def next() = resultSet 
    }.toStream 
    } 

tôi cần phải truy cập vào bảng siêu dữ liệu, nhưng điều này sẽ làm việc cho hàng bảng (có thể làm một stmt.executeQuery (sql) thay vì md.getColumns): function

val md = connection.getMetaData() 
val columnItr = resultSetItr(md.getColumns(null, null, "MyTable", null)) 
     val columns = columnItr.map(col => { 
     val columnType = col.getString("TYPE_NAME") 
     val columnName = col.getString("COLUMN_NAME") 
     val columnSize = col.getString("COLUMN_SIZE") 
     new Column(columnName, columnType, columnSize.toInt, false) 
     }) 
+1

Nếu bạn không cần quay lại luồng (ví dụ: chỉ chuyển tiếp lặp), bạn chỉ có thể sử dụng trình lặp. Điều này làm giảm đáng kể chi phí bộ nhớ của việc sử dụng một dòng (trả về một 'Iterator [ResultSet]', và thả 'toStream') – Greg

8

Utility cho câu trả lời @ elbowich của:

def results[T](resultSet: ResultSet)(f: ResultSet => T) = { 
    new Iterator[T] { 
    def hasNext = resultSet.next() 
    def next() = f(resultSet) 
    } 
} 

Cho phép bạn sử dụng suy luận kiểu. Ví dụ .:

stmt.execute("SELECT mystr, myint FROM mytable") 

// Example 1: 
val it = results(stmt.resultSet) { 
    case rs => rs.getString(1) -> 100 * rs.getInt(2) 
} 
val m = it.toMap // Map[String, Int] 

// Example 2: 
val it = results(stmt.resultSet)(_.getString(1)) 
2

Vì ResultSet chỉ là một đối tượng có thể thay đổi được lèo lái bởi tiếp theo, chúng ta cần phải xác định khái niệm riêng của chúng ta về một hàng bên cạnh. Chúng ta có thể làm như vậy với một chức năng đầu vào như sau:

class ResultSetIterator[T](rs: ResultSet, nextRowFunc: ResultSet => T) 
extends Iterator[T] { 

    private var nextVal: Option[T] = None 

    override def hasNext: Boolean = { 
    val ret = rs.next() 
    if(ret) { 
     nextVal = Some(nextRowFunc(rs)) 
    } else { 
     nextVal = None 
    } 
    ret 
    } 

    override def next(): T = nextVal.getOrElse { 
    hasNext 
    nextVal.getOrElse(throw new ResultSetIteratorOutOfBoundsException 
)} 

    class ResultSetIteratorOutOfBoundsException extends Exception("ResultSetIterator reached end of list and next can no longer be called. hasNext should return false.") 
} 

EDIT: Dịch dòng hay cái gì khác theo ở trên.

5

Điều này nghe có vẻ giống như một cơ hội tuyệt vời cho một lớp học ngầm định. Đầu tiên xác định các lớp tiềm ẩn ở đâu đó:

import java.sql.ResultSet 

object Implicits { 

    implicit class ResultSetStream(resultSet: ResultSet) { 

     def toStream: Stream[ResultSet] = { 
      new Iterator[ResultSet] { 
       def hasNext = resultSet.next() 

       def next() = resultSet 
      }.toStream 
     } 
    } 
} 

Tiếp theo, bạn chỉ cần nhập lớp ngầm này bất cứ nơi nào bạn đã thực hiện truy vấn của bạn và xác định đối tượng ResultSet:

import com.company.Implicits._ 

Cuối cùng lấy dữ liệu bằng cách sử dụng phương pháp toStream. Ví dụ: lấy tất cả các id như được hiển thị bên dưới:

val allIds = resultSet.toStream.map(result => result.getInt("id")) 
Các vấn đề liên quan