How to search a MongoDB collection with Scala and Casbah

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 16.5, “How to search a MongoDB collection with Scala and Casbah.”

Problem

You want to find objects in your MongoDB collection using Scala and the Casbah driver.

Solution

Use the find* methods of the MongoCollection class to get the elements you want, specifically the find and findOne methods.

Assuming that you have everything set up as shown in Recipe 16.3, the following code demonstrates these techniques:

  • How to find all the documents in a collection
  • How to find one document that matches your search criteria
  • How to find all documents that match your search criteria
  • How to limit the number of results returned by a find query

Here’s the code:

import com.mongodb.casbah.Imports._

object Find extends App {
    val collection = MongoFactory.collection

    // (1) find all stocks with find()
    // -------------------------------
    println("\n___ all stocks ___")
    var stocks = collection.find
    stocks.foreach(println)

    // (2) search for an individual stock
    // ----------------------------------
    println("\n___ .findOne(query) ___")
    val query = MongoDBObject("symbol" -> "GOOG")
    val result = collection.findOne(query)          // Some
    val stock = convertDbObjectToStock(result.get)  // convert it to a Stock
    println(stock)

    // (3) find all stocks that meet a search criteria
    // -----------------------------------------------
    println("\n___ price $gt 500 ___")
    stocks = collection.find("price" $gt 500)
    stocks.foreach(println)

    // (4) find all stocks that match a search pattern
    // -----------------------------------------------
    println("\n___ stocks that begin with 'A' ___")
    stocks = collection.find(MongoDBObject("symbol" -> "A.*".r))
    stocks.foreach(println)

    // (5) find.limit(2)
    // -------------------------------
    println("\n___ find.limit(2) ___")
    stocks = collection.find.limit(2)
    stocks.foreach(println)

    // warning: don't use the 'get' method in real-world code
    def convertDbObjectToStock(obj: MongoDBObject): Stock = {
        val symbol = obj.getAs[String]("symbol").get
        val price = obj.getAs[Double]("price").get
        Stock(symbol, price)
    }
}

Save that code to a file named Find.scala in the root directory of your SBT project, and then run the object with SBT:

$ sbt run

If you’ve been working through the MongoDB recipes in this chapter, or you cloned my Scala + Casbah + MongoDB project from GitHub, you may have multiple main methods in your project. If so, SBT detects those main methods and asks which one you want to run. To run the Find object, select it from the list SBT displays:

Multiple main classes detected, select one to run:

[1] Find
[2] Insert
[3] Insert2

Enter number: 1

Running the Find object after populating the database in the earlier recipes results in the following output:

___ all stocks ___
{ "_id" : { "$oid" : "502683283004b3802ec47df2"} , "symbol" : "AAPL" ,
  "price" : 600.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df3"} , "symbol" : "GOOG" ,
  "price" : 650.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df4"} , "symbol" : "NFLX" ,
  "price" : 60.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df5"} , "symbol" : "AMZN" ,
  "price" : 220.0}

___ .findOne(query) ___
Stock(GOOG,650.0)

___ price $gt 500 ___
{ "_id" : { "$oid" : "502683283004b3802ec47df2"} , "symbol" : "AAPL" ,
  "price" : 600.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df3"} , "symbol" : "GOOG" ,
  "price" : 650.0}

___ stocks that begin with 'A' ___
{ "_id" : { "$oid" : "502683283004b3802ec47df2"} , "symbol" : "AAPL" ,
  "price" : 600.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df5"} , "symbol" : "AMZN" ,
  "price" : 220.0}

___ find.limit(2) ___
{ "_id" : { "$oid" : "502683283004b3802ec47df2"} , "symbol" : "AAPL" ,
  "price" : 600.0}
{ "_id" : { "$oid" : "502683283004b3802ec47df3"} , "symbol" : "GOOG" ,
  "price" : 650.0}

Discussion

In the first query, the find method returns all documents from the specified collection. This method returns a MongoCursor, and the code iterates over the results using that cursor.

In the second query, the findOne method is used to find one stock that matches the search query. The query is built by creating a MongoDBObject with the desired attributes. In this example, that’s a stock whose symbol is GOOG. The findOne method is called to get the result, and it returns an instance of Some[MongoDBObject].

In this example, result.get is called on the next line, but in the real world, it’s a better practice to use a for loop or a match expression:

collection.findOne(query) match {
    case Some(Stock) =>
         // convert it to a Stock
         println(convertDbObjectToStock(result.get))
    case None =>
         println("Got something else")
}

Of course, how you implement that will vary depending on your needs.

The convertDbObjectToStock method does the reverse of the buildMongoDbObject method shown in the earlier recipes, and converts a MongoDBObject to a Stock instance.

The third query shows how to search for all stocks whose price is greater than 500:

stocks = collection.find("price" $gt 500)

This again returns a MongoCursor, and all matches are printed.

Casbah includes other methods besides $gt, such as $gte, $lt, and $lte. You can use multiple operators against one field like this:

"price" $gt 50 $lte 100

You can also query against multiple fields by joining tuples:

val query: DBObject = ("price" $gt 50 $lte 100) ++ ("priceToBook" $gt 1)

See the Casbah documentation for more examples of creating Casbah-style queries.

In the fourth query, a simple regular expression pattern is used to search for all stocks whose symbol begins with the letter A:

stocks = collection.find(MongoDBObject("symbol" -> "A.*".r))

Notice that the r method is called on a String to create the query. This converts the String to a Regex, as demonstrated in the REPL:

scala> "A.*".r
res0: scala.util.matching.Regex = A.*

The fifth query demonstrates how to use the limit method to limit the number of results that are returned:

stocks = collection.find.limit(2)

Because MongoDB is typically used to store a lot of data, you’ll want to use limit to control the amount of data you get back from a query.

The MongoCollection class also has a findByID method that you can use when you know the ID of your object. Additionally, there are findAndModify and findAndRemove methods, which are discussed in other recipes in this chapter.

See Also