2010-01-20 6 views
14

J'ai un ensemble de lignes dans une base de données, et je voudrais fournir une interface pour filer à travers eux comme ceci:Scala: L'exposition d'un ResultSet JDBC par un générateur (itérables)

def findAll: Iterable[MyObject] 

Où nous n'avons pas besoin d'avoir toutes les instances en mémoire à la fois. En C#, vous pouvez facilement créer des générateurs de ce type en utilisant yield, le compilateur s'occupe de convertir du code qui traverse le jeu d'enregistrements en un itérateur (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 un pour la compréhension?

Répondre

13

Essayez d'étendre Iterator à la place. 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 autrement juste un emballage léger pour les appels vers rs.

Si vous souhaitez enregistrer les valeurs que vous parcourez, sélectionnez Flux.

+0

Je vais donner un coup de feu, thx Rex –

+0

J'ai juste supposé que rs avait en fait une méthode "hasNext". Sinon, vous devriez mettre en cache le résultat suivant avec un var (probablement privé) à l'intérieur de l'itérateur et avoir hasNext dire si ce résultat en cache existe. –

+1

Ouais, cela fonctionne, j'ai utilisé hasNext = rs.isLast. Un problème est que je n'ai aucun mécanisme pour fermer le RS et la connexion. Dans mon code actuel, j'ai enveloppé le code (ci-dessus) dans une méthode "using" qui ferme les choses pour moi. –

12

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 l'ensemble de résultats - qui était mon intention personnelle:

val x = new RsIterator(resultSet).map(x => { 
    (x.getString("column1"), x.getInt("column2")) 
}) 

un .toList Append afin de forcer l'évaluation. Cela est utile si la connexion à la base de données est fermée avant d'utiliser les valeurs. Sinon, vous obtiendrez une erreur indiquant que vous ne pouvez pas accéder au ResultSet après la fermeture de la connexion.

+0

J'ai utilisé cette solution. Merci beaucoup! J'ai dû appeler toList pour utiliser les résultats. val externalKeys = resultSet.map {x => x.getString ("clé_externe") } .toListe – JasonG

+0

Il y a un problème avec ceci. Selon les spécifications de hasNext, il devrait s'exécuter sans avancer le jeu de résultats.Ainsi, soit la mise en cache comme suggéré par @Rex Kerr ou l'utilisation d'isLast (pas sûr de la performance) est un meilleur choix là-bas. – Sohaib

6

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 de .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 .

Questions connexes