Scala: Exposer un ResultSet JDBC à travers un générateur (iterable)
Question
J'ai un ensemble de lignes dans une base de données, et je voudrais fournir une interface pour filer à travers eux comme ceci:
def findAll: Iterable[MyObject]
Si nous ne demandons pas d'avoir toutes les instances en mémoire à la fois. En C #, vous pouvez facilement créer des générateurs comme celui-ci en utilisant le rendement, le compilateur prend en charge la conversion du code qui boucle à travers le jeu d'enregistrements dans un iterator (sorte de l'inverser).
Mon code actuel ressemble à ceci:
def findAll: List[MyObject] = {
val rs = getRs
val values = new ListBuffer[MyObject]
while ( rs.next() )
values += new valueFromResultSet(rs)
values.toList
}
Est-il possible que je pourrais convertir ce pour ne pas stocker l'ensemble dans la mémoire? Peut-être que je pourrais utiliser une pour la compréhension?
La solution
Essayez étendre à la place Iterator. Je ne l'ai pas testé, mais quelque chose comme ceci:
def findAll: Iterator[MyObject] = new Iterator[MyObject] {
val rs = getRs
override def hasNext = rs.hasNext
override def next = new valueFromResultSet(rs.next)
}
Cela devrait stocker rs quand il est appelé, et sinon juste un emballage léger pour les appels vers rs.
Si vous souhaitez enregistrer les valeurs que vous traversez, consultez Stream.
Autres conseils
Je suis tombé sur le même problème et sur la base des idées ci-dessus, j'ai créé la solution suivante en écrivant une classe d'adaptateur:
class RsIterator(rs: ResultSet) extends Iterator[ResultSet] {
def hasNext: Boolean = rs.next()
def next(): ResultSet = rs
}
Avec cela, vous pouvez par exemple effectuer carte opérations sur le jeu de résultats - qui était mon intention personnelle:
val x = new RsIterator(resultSet).map(x => {
(x.getString("column1"), x.getInt("column2"))
})
Ajoute une .toList
afin de forcer l'évaluation. Ceci est utile si la connexion de base de données est fermée avant d'utiliser les valeurs. Sinon, vous obtiendrez une erreur en disant que vous ne pouvez pas accéder au ResultSet après la connexion a été fermée.
Une simple (idiomatiques) façon d'obtenir le même serait
Iterator.continually((rs.next(), rs)).takeWhile(_._1).map(r => valueFromResultSet(r._2)).toList
Vous avez besoin du .toList
pour forcer l'évaluation, sinon la collection sous-jacente sera un cours d'eau et le ResultSet peut être fermé avant l'évaluation a eu lieu.