8

I want to loop through documents in MongoDB. Basically here is the situation. I have some JTextfields which i want to populate from MongoDB. So each time the user click on the Next button, a new record must be fetched and display it in the JTextField. Here's my code:

public class nextstud implements ActionListener
{
    public void actionPerformed(ActionEvent e) {
        try {
            Mongo s = new Mongo();
            DB db = s.getDB( "omrs1" );
            DBCollection coll = db.getCollection("Student") ;

            DBCursor curs = coll.find();

            if(curs.hasNext()) {
                DBObject o = curs.next();
                String fname = (String) o.get("Firstname") ; 
                String lname = (String) o.get("Lastname") ; 
                String sid = (String) o.get("StudentID") ; 
                String prg = (String) o.get("Programme") ;
                String lvl = (String) o.get("Level") ;

                txtfname.setText(fname) ; 
            }

            btndelstud.setEnabled(true); 
            btnbkstud.setEnabled(true) ;
            btnfwdstud.setEnabled(true);

        } catch (UnknownHostException x) {
            x.printStackTrace();
        } catch (MongoException x) {
            x.printStackTrace();
        }
    }
} // end class

However, it does not work. It only displays the first record each time i press the next button. If i change

if(curs.hasNext()) {

to

while(curs.hasNext()) {

It displays only the last record. Help please?

Beryllium
  • 12,808
  • 10
  • 56
  • 86

2 Answers2

12

As Kevin mentioned, the problem is that you're fetching a new cursor on each button press, so it's always starting over. There are two potential approaches that will fix this problem.

  • Fetch the cursor once, and move through it as next is pushed. To do this, you make the cursor a field, and fetch the cursor in the listener's constructor.

    public class Nextstud implements ActionListener {
        private DBCursor curs;
        public Nextstud() {
            Mongo s = new Mongo();
            DB db = s.getDB( "omrs1" );
            DBCollection coll = db.getCollection("Student") ;
    
            curs = coll.find();
        }
    
        public void actionPerformed(ActionEvent e) {
            try {
                if(curs.hasNext()) {
                    DBObject o = curs.next();
                    String fname = (String) o.get("Firstname") ; 
                    String lname = (String) o.get("Lastname") ; 
                    String sid = (String) o.get("StudentID") ; 
                    String prg = (String) o.get("Programme") ;
                    String lvl = (String) o.get("Level") ;
    
                    txtfname.setText(fname) ; 
                }
    
                btndelstud.setEnabled(true); 
                btnbkstud.setEnabled(true) ;
                btnfwdstud.setEnabled(true);
            } catch (UnknownHostException x) {
                x.printStackTrace();
            } catch (MongoException x) {
                x.printStackTrace();
            }
        }
    } // end class
    
  • The next alternative is to keep a count of how many items have been retrieved, and update the cursor's skip count:

    DBCursor foo = coll.find().skip(count).limit(1);
    count++;
    //use one value from the cursor as before
    

The first approach is likely to be slightly faster. Mongo can do this iteration using a single tree traversal (as opposed to many for the second approach).

The second approach doesn't keep a cursor open between button clicks. This sort of thing is important for scalability on web apps between requests, but might not matter as much with a gui app (especially if the number of concurrent users is smaller).

Another big advantage of the second approach is that you can go backwards — DBCursor doesn't have a previous() method, so you'll need to use this approach if you ever add a Previous button.

Some other things you should probably do:

  • Add a layer of indirection so that your GUI event handling code and your MongoDB data access code aren't quite so highly coupled. This will save you a bunch of trouble if you move to a different database (perhaps unlikely), or add a previous button that integrates with the same query (perhaps more likely).

  • Remember to close the cursor when you're done with it. DBCursor implementations leak, and need to be cleaned up with a timeout scheme if you don't explicitly close them. This is especially true if you don't completely iterate through the entire result set. This goes for the Mongo instance as well, but you'll only need a single one of those for the entire application.

Beryllium
  • 12,808
  • 10
  • 56
  • 86
Sean Reilly
  • 21,526
  • 4
  • 48
  • 62
  • Thank you Sean. I use DBCursor foo = coll.find().skip(count).limit(1); count++; to fix this. Regards –  Mar 12 '12 at 15:31
  • Btw, how do i implement the previous button? –  Mar 12 '12 at 15:46
  • @Mozammil: In a nutshell: use the second approach, but subtract from the count variable, instead of adding to it. – Sean Reilly Mar 12 '12 at 15:51
3

The problem is that you're fetching a new cursor each time. So it only shows the first record. I assume you have more than one text field, if you want to do all initialization in one function, you'll need to operate on an array of textfields, not a single one. Or, if the intention is to call actionPerformed several times (once for each textField) then you should maintain some state (e.g. the cursor) between calls to actionPerformed. I don't do a lot of GUI programming however, so I'm not sure which is correct based on the code snippet you've provided.

Kevin
  • 24,871
  • 19
  • 102
  • 158