Introducing Spring Roo, Part 7: Develop Spring MongoDB applications using Spring Roo

Build enterprise Spring MongoDB applications rapidly

MongoDB is a very popular document-oriented, horizontally-scalable NoSQL datastore. With Spring Roo version 1.2 you can build Spring applications with MongoDB as data storage solutions. You will first look at MongoDB and then will build an enterprise Spring MongoDB application using Spring Roo.

Shekhar Gulati, Senior Java Consultant, Xebia

Photo of Shekar GulatiShekhar Gulati is a Java consultant working with Xebia India. He has six years of enterprise Java experience. He has extensive experience in Spring portfolio projects, such as Spring, Spring-WS, and Spring Roo. His interests are Spring, NoSQL databases, Hadoop, RAD frameworks like Spring Roo, cloud computing (mainly PaaS services like Google App Engine, CloudFoundry, OpenShift), Hadoop. He is an active writer and writes for JavaLobby, Developer.com, IBM developerWorks and his own blog at http://whyjava.wordpress.com/. You can follow him on twitter @ http://twitter.com/#!/shekhargulati.



07 September 2012

Also available in Russian Japanese Portuguese

Part 6 of this series on Spring Roo looked at a lot of new features introduced in Spring Roo 1.2. One of the features I talked about was support for building MongoDB applications. Spring Roo MongoDB aims to bring the classic Spring proposition of enhanced productivity and a consistent programming model to MongoDB applications. Spring MongoDB is a sub-project of Spring Data. Spring Data is an umbrella open-source project which contains many sub-projects specific to given data stores. I will first introduce MongoDB and then you will build a Spring MongoDB application using Spring Roo.

Introducing MongoDB

In a single line, MongoDB is an open-source, document-oriented, schema-free, fast-and-horizontally-scalable NoSQL datastore. This description contains five keywords which are very important to understand. Let's take a look at them one by one :

  1. Document-oriented: In MongoDB there is no concept of row as in relational databases, but there is the concept of a document. MongoDB stores data in the form of binary JSON documents, or BSON, which maps very closely with domain objects. MongoDB does not have any concept of joins but you have nested objects. Take the example of a blog application which has two domain objects: blog and comment. In a relational database, a blog will have a one-to-many relationship with the comment table and you require a join to fetch comments for a blog. In MongoDB, joins are avoided by using nested documents. The example in Listing 1 is a blog document which has a comment array. Without any joins, you can fetch all the comments for a blog. MongoDB provides rich query capabilities so you can easily filter out the required fields and elements.

    Listing 1. Example of a nested array
    { 
        "author" : "shekhar", 
        "text" : "Hello MongoDB", 
        "title" : "Hello MongoDB" 
        "comments" : [ 
            { 
                "name" : "anonymous", 
                "comment" : "good blog" 
            }, 
            { 
                "name" : "guest", 
                "comment" : "awesome blog" 
            } 
        ], 
    }
  2. Schema-free: MongoDB stores documents in collections as rows are stored in tables in RDBMS. But MongoDB does not force you to define a rigid schema for your collection. Every document in MongoDB can be totally different from the already saved document in a collection. Each document in a MongoDB collection is heterogeneous, and can have completely a different structure compared to other documents. This means you can store both the blog and author documents in the same collection as in Listing 2.

    Listing 2. Example of a MongoDB Collection
    {"author" : "shekhar", "text" : "Hello MongoDB","title" : "Hello MongoDB"}
    {"fullname":"Shekhar Gulati","email":"shekhargulati84@gmail.com","password":"xxxxxx"}
  3. Fast: MongoDB is high-performance datastore, both in terms of writes and reads. With the default configuration, write performance for MongoDB is higher as compared to RDBMS because it is fire and forget, written to RAM then to disk. To control the write behavior in MongoDB, specify a value of WriteConcern along with your write operation. Look at the different values of WriteConcern.

    1. Normal: This is the default option where every write operation is fire and forget, meaning it just writes to the driver and returns. It does not wait for write to be available on the server. So, if another thread tries to read the document just after the document has been written it might not find it. There is a very high probability of data loss with this option. Do not consider this option where data durability is important and you only use a single instance of the MongoDB server.

    2. None: This is almost same as Normal with one difference. In Normal, you get an exception if the network goes down or another network issue occurs. With None you get no exception with network issues. This makes it highly unreliable.

    3. Safe: As suggested by its name this option is safer than Normal or None options. The write operation waits for the MongoDB server to acknowledge the write, but data is still not written to disk. With Safe you will not face the issue of another thread trying to read the object you just wrote and not finding it. It provides a guarantee that objects, once written, will be found. That is good, but you can still lose data because if data is not written to disk and the server dies for some reason the data will be lost.

    4. Journal Safe: Before I talk about this option, let's first talk about what journalling is in MongoDB. Journalling is a feature of MongoDB where a write ahead log file is maintained for all operations. In scenarios where MongoDB is not cleanly shut down, like using the kill -9 command, the data can be recovered from journal files. By default data is written to journal files after every 100 milliseconds (ms). You can change it to between 2 ms and 300 ms. With version 2.0 journalling is enabled by default on 64-bit MongoDB servers. With the Journal Safe write concern option, your write will wait until the journal file is updated.

    5. Fysnc: With the Fsync write concern, write operations wait for the server to flush the data to disk. This is the safest option on a Single node as you only lose data when the hard disk crashes.

    I wrote a detailed blog post entitled "How MongoDB Different Write Concern Values Affect Performance On A Single Node?" (see Resources). Read that for more details.

  4. Horizontally-scalable: MongoDB is a horizontally-scalable datastore, meaning that it handles more reads/writes and can add more MongoDB servers to the cluster. To achieve horizontal scalability MongoDB uses sharding, that is adds more MongoDB instances, to handle increasing load and data without affecting application performance. The best part is that applications do not have to do sharding; MongoDB handles it automatically. Auto-sharding is not in the scope of this article. See Resources for a link to the MongoDB documentation on sharding.

  5. NoSQL: You probably have heard the term NoSQL over the last couple of years or more. NoSQL is an acronym for Not Only SQL. NoSQL refers to a broad range of data stores which do not follow the RDBMS model and do not use SQL as their primary query language. MongoDB supports query language, but it is not like SQL and based on JSON documents.

MongoDB supports similar operations to RDBMS, like indexes, queries, explain plan, and aggregation functions like group, to remain close to RDBMS and lower the learning curve. Another cool feature supported by MongoDB is the ability to execute Map Reduce jobs and geospatial indexing and querying (see documentation in Resources).

MongoDB Terminology

MongoDB terminology is very simple and easy to understand and remember. Table 1 shows a comparison with relational databases.

Table 1. Comparing MongoDB terms with relational database terms
RDBMSMongoDB

Database

Database

Table

Collection

Row

Document

In MongoDB everything resides in a database and you can create a database using a command. A database in MongoDB can have multiple collections just as a database in RDBMS can have multiple tables. A collection in MongoDB can have multiple documents just like a table in RDBMS can have multiple rows. The difference being that in RDBMS a row has a fixed structure which is true across all members whereas in MongoDB a document has a loose structure and two documents in a collection can have entirely different structures.


Play with MongoDB

Before you work with MongoDB, follow the four-step process to download and run a MongoDB server.

  1. Download MongoDB: You can download the tarball or zip of the latest version (currently 2.0.4) of MongoDB for your operating system from the MongoDB website (see Resources).

  2. Once you download MongoDB, extract the zip to a convenient location and make the shell files executable.

  3. From the command line, go inside the bin folder of the extracted MongoDB installation and execute mongod. By default MongoDB stores all the data in /data/db so you might need to create the directory and make your user the owner. You can also choose not to use the default /data/db directory by specifying the location with the dbpath attribute as in Listing 3.

Listing 3. Starting MongoDB
shekhar@shekhar:~/tools/mongodb/mongodb2/bin$ ./mongod 
--dbpath=/home/shekhar/data/db 
Sat Mar 31 19:16:48 
Sat Mar 31 19:16:48 warning: 32-bit servers don't have journalling enabled by 
default. Please use --journal if you want durability. 
Sat Mar 31 19:16:48 
Sat Mar 31 19:16:48 [initandlisten] MongoDB starting : pid=6033 port=27017 
dbpath=/home/shekhar/data/db 32-bit host=shekhar 
Sat Mar 31 19:16:48 [initandlisten] 
Sat Mar 31 19:16:48 [initandlisten] ** NOTE: when using MongoDB 32 bit, you 
are limited to about 2 gigabytes of data 
Sat Mar 31 19:16:48 [initandlisten] ** see 
http://blog.mongodb.org/post/137788967/32-bit-limitations 
Sat Mar 31 19:16:48 [initandlisten] ** with --journal, the limit is lower 
Sat Mar 31 19:16:48 [initandlisten] 
Sat Mar 31 19:16:48 [initandlisten] db version v2.0.1, pdfile version 4.5 
Sat Mar 31 19:16:48 [initandlisten] git version: 
3a5cf0e2134a830d38d2d1aae7e88cac31bdd684 
Sat Mar 31 19:16:48 [initandlisten] build info: Linux domU-12-31-39-01-70-B4 
2.6.21.7-2.fc8xen #1 SMP Fri Feb 15 12:39:36 EST 2008 i686 BOOST_LIB_VERSION=1_41 
Sat Mar 31 19:16:48 [initandlisten] options: { dbpath: "/home/shekhar/data/db" } 
Sat Mar 31 19:16:48 [websvr] admin web console waiting for connections on port 28017 
Sat Mar 31 19:16:48 [initandlisten] waiting for connections on port 27017

Once you start the MongoDB server you can view some basic information on its web console available at http://localhost:28017/. To perform useful operations, you need a client. MongoDB provides a command line client called mongo which you can find in bin folder.

T0 start the client, execute ./mongo and you will see the output in Listing 4.

Listing 4. Starting the MongoDB client
shekhar@shekhar:~/tools/mongodb/mongodb2/bin$ ./mongo 
MongoDB shell version: 2.0.1 
connecting to: test

The output in Listing 4 shows that the mongo client has connected to the test database. Once you are connected with the server you can perform various operations like inserting a document in a collection and finding the document.

Create Database

To start off lets create a database. MongoDB does not have a command to create a database. You just need to select a database with the use command and database will get created for you as in Listing 5.

Listing 5. Selecting a database
> use springroopart7
switched to db springroopart7

The command in Listing 5 creates a database named springroopart7 and switches the context to newly created database. This command will create the database only when it does not exist otherwise it just switches the client to the existing database.

Create collections

Once you create a database, your next logical step is to create a collection inside a database. You can create the collection using the db.createCollection() operation as in Listing 6.

Listing 6. Creating a collection
> db.createCollection("blogs") 
{ "ok" : 1 }

In Listing 6, you executed the operation to create a collection named "blogs" and MongoDB responded that it has successfully created the collection.

The other way to create a collection is to save the document into a collection. Then MongoDB creates a collection if it does not exist. Otherwise MongoDB will add a document to existing collection. This is covered next.

Inserting documents

Now that you have a blogs collection, insert a blog in the collection. You need to create JSON document and insert the document as in Listing 7.

Listing 7. Example of inserting JSON
> db.blogs.insert({"author":"shekhar","title" : "Getting started with MongoDB",
"text":"MongoDB is an open-source document oriented, schema-free, fast and horizontally 
        scalable NoSQL datastore", 
"tags":["mongodb","nosql"]})

The command in Listing 7 creates a new blog document and inserts it into the blogs collection. As you can see the document is quite rich as it contains array field tags. You can easily query for all the blogs corresponding to a particular tag. You can have fields of text type, numeric type, date type, arrays, and more.

Finding documents

As you have only persisted a single document, you can view the document using the findOne operation which will return any one document in this collection or the first document matching the specified query. To find any one document inside the blogs collection, execute the command in Listing 8.

Listing 8. Example output of retrieving a document
> db.blogs.findOne() 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

If you want to find only one document whose author is "shekhar," execute db.blogs.findOne({"author":"shekhar"})

To query on tags, execute the command in Listing 9. As you can see, it looks the same as the previous queries.

Listing 9. Example output of findOne with a specified query
> db.blogs.findOne({"tags":"mongodb"}) 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

The above queries will return only one document. If you want to find all the documents whose author is shekhar with tags of mongodb, you can use the find method: > db.blogs.find({"author":"shekhar","tags":"mongodb"})

One thing that you should know is that the find method returns a cursor not a document. By default in MongoDB client, you see the first ten documents. To view all the documents, iterate over the cursor (see Listing 10).

Listing 10. Example of displaying documents with multiple iterations
> var cursor = db.blogs.find({"author":"shekhar","tags":"mongodb"}) 
> while(cursor.hasNext())printjson(cursor.next()) 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

Other commands

You can execute a lot of other commands on MongoDB. To see all the operations available on a collection, type the help command as in Listing 11. For brevity I have not shown all the commands. You will see them all when executing in your own mongo client.

Listing 11. Sample output of help command
> db.blogs.help() 
DBCollection help
db.blogs.find().help() - show DBCursor help 
db.blogs.count() 
db.blogs.dataSize() 
db.blogs.distinct( key ) - for example db.blogs.distinct( 'x' ) 
db.blogs.drop() drop the collection 
db.blogs.dropIndex(name) 
db.blogs.dropIndexes() 
db.blogs.ensureIndex(keypattern[,options]) - options is an object with these 
possible fields: name, unique, dropDups 
db.blogs.reIndex() 
db.blogs.find([query],[fields]) - query is an optional query filter. fields is 
optional set of fields to return.

Now that you have a basic understanding of MongoDB, you can move forward to create a Spring application using MongoDB as a datastore. You can refer to the MongoDB documentation or other MongoDB articles published on developerWorks (see Resources).


Schema design

The application that you will create is called ShortNotes. It has the two domain objects: Notebook and Note. A notebook can have many notes. There are several ways to design the schema for this application. Let's look at different Schema Design choices.

Notebook with an array of embedded Notes

A Notebook document with a nested, embedded array of Note documents looks to be the most obvious schema design for a document datastore. This design avoids joins and provides a rich domain model. Listing 12 shows a notebook collection which has two notebook documents with an embedded array of notes documents.

Listing 12. Example of an embedded array schema
{ 
    "_id" : ObjectId("4f7ff4c9c2fceff338eb9a82"), 
    "name" : "MongoDB Notebook", 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T08:02:59.572Z"), 
    "notes" : [ 
        { 
             "_id" :"f7ff4c9c2fceff338eb9a443"
            "title" : "Getting Started with MongoDB", 
            "note" : "Getting Started with MongoDB", 
            "created" : ISODate("2012-04-07T08:02:59.572Z") 
        }, 
        { 
            "_id" :"efvvgf7ff4c9ceff338eb9a443"
            "title" : "Setting up Replication", 
            "note" : "Setting up Replica Set in MongoDB", 
            "created" : ISODate("2012-04-07T08:02:59.572Z") 
        }
    ] 
} 
{ 
    "_id" : ObjectId("4f7ff4dcc2fceff338eb9a83"), 
    "name" : "MongoDB Notebook", 
    "author" : "tom", 
    "created" : ISODate("2012-04-07T08:03:31.491Z"), 
    "notes" : [ 
        { 
            "_id" :"be7ff4c332fceff338eb9a443"
            "title" : "Getting Started with MongoDB", 
            "note" : "Getting Started with MongoDB", 
            "created" : ISODate("2012-04-07T08:03:31.491Z") 
        }, 
        { 
            "_id" :"aaff4c9c3fceff338eb9a443"
            "title" : "Setting up Sharding", 
            "note" : "Setting up Sharded Cluster in MongoDB", 
            "created" : ISODate("2012-04-07T08:03:31.491Z") 
        } 
    ] 
}

The schema in Listing 12 looks good and you can query for notebooks and notes.

To find all the notebooks with name MongoDB Notebook and author shekhar you can write the following query:

 db.notebooks.find({"name":"MongoDB Notebook","author":"shekhar"})

You can also very easily query for note with this command:

db.notebooks.findOne({"notes._id":"be7ff4c332fceff338eb9a443"})

Things get a bit complex when you must update a specific note document in the array or you need to delete a specific element from the array. These can be achieved using $set, $pull modifiers, but they require you to have good knowledge of them. The example in Listing 13 removes the note with _id f7ff4c9c2fceff338eb9a443 and notebook with author shekhar.

Listing 13. Example of removing a note with $set, $pull modifiers
> db.notebooks.update({"author":"shekhar"},{"$pull":{"notes":
{"_id":"f7ff4c9c2fceff338eb9a443"}}}) 
> 
> 
> db.notebooks.findOne({"author":"shekhar"}) 
{ 
    "_id" : ObjectId("4f80137fc2fceff338eb9a8a"), 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T10:14:01.827Z"), 
    "name" : "MongoDB Notebook", 
    "notes" : [ 
        { 
            "_id" : "efvvgf7ff4c9ceff338eb9a443", 
            "title" : "Setting up Replication", 
            "note" : "Setting up Replica Set in MongoDB", 
            "created" : ISODate("2012-04-07T10:14:01.827Z") 
        } 
    ] 
}

The schema has a few issues.

  1. The first issue with the schema is that you can't retrieve a specific note. For example, if you want to find a note with the _id of "f7ff4c9c2fceff338eb9a443", you might think that you can easily query for the note as in Listing 14.

    Listing 14. Example of attempting to remove a note based on its ID
    > db.notebooks.find({"notes._id":"f7ff4c9c2fceff338eb9a443"})
    >{ 
        "_id" : ObjectId("4f7ff9edc2fceff338eb9a84"), 
        "name" : "MongoDB Notebook", 
        "author" : "shekhar",
        "created" : ISODate("2012-04-07T08:24:41.782Z"),
        "notes" : [ 
            { 
                "_id" : "f7ff4c9c2fceff338eb9a443",
                "title" : "Getting Started with MongoDB", 
                "note" : "Getting Started with MongoDB",
                "created" : ISODate("2012-04-07T08:24:41.782Z")
            },
            {
                "_id" : "efvvgf7ff4c9ceff338eb9a443",
                "title" : "Setting up Replication",
                "note" : "Setting up Replica Set in MongoDB",
                "created" : ISODate("2012-04-07T08:24:41.782Z")
            }
        ]
    }

    This will return the notebook object with all notes in it. You will have to do all the filtering on the client side to get the required note object. This feature request has been logged in MongoDB jira (see Resources) and you might see this feature in future.

  2. The second problem that you might face is when you have thousands of notes in a single notebook document. Writes to MongoDB might fail because the size restriction for a single MongoDB document is 16 MB. If your single document is bigger than 16MB, think about normalizing your schema so you have multiple collections.

  3. The third issue is that when you have thousands of notes and each note is large in size. You might face memory issues on the client side. For such a scenario, think about pagination using the MongoDB $slice operator.

Relational approach: Notebook and Note as separate collections

The second approach to designing schema for the use case is to have separate collections for Notebook and Notes with each note document containing a reference to its notebook. This might look to be against MongoDB schema design as you move towards the RDBMS paradigm. Yet this is one of the suggested approaches in some use cases. The point is that you are not forced to use either approach. The design should be governed by your application use case. Since Notebook is a very simple document with just couple of fields you might have each note document contain a notebook document instead of just a notebook identifier. This avoids cross collection interaction and simplifies querying. Listing 15 shows the notebook document.

Listing 15. Example of a simple notebook document
{ 
    "name" : "MongoDB Notebook", 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T09:36:01.062Z") 
}

The note in "MongoDB Notebook" would look like that in Listing 16.

Listing 16. Example note document
{ 
    "_id" : ObjectId("4f800b5cc2fceff338eb9a87"), 
    "title" : "Getting Started with MongoDB", 
    "note" : "Getting Started with MongoDB", 
    "created" : ISODate("2012-04-07T09:38:42.462Z"), 
    "notebook" : { 
        "_id" : ObjectId("4f800af3c2fceff338eb9a86"), 
        "name" : "MongoDB Notebook", 
        "author" : "shekhar", 
        "created" : ISODate("2012-04-07T09:36:01.062Z") 
    } 
}

With this design you can easily query both notes and notebooks collections. To find all the notes in the Notebook with the name "MongoDB Notebook," write the following query: db.notes.findOne({"notebook.name":"MongoDB Notebook"})

You can also create an index on notebook.name to make the queries read very fast: db.notes.ensureIndex({"notebook.name":1})

This schema design has one issue—if you modify a document in a Notebooks collection you have to manually modify its references in the Notes collection.

For this application, you use the relational approach with two separate collection for Notes and Notebooks.


Installing Spring Roo

Before you start to build the application, make sure you have Spring Roo 1.2.1 on your machine. To install the stand-alone Spring Roo:

  1. Download the Roo stand-alone command-line shell or use the built-in Roo SpringSource Tool Suite (STS) plug-in (see Resources). I suggest that you download both and use them together because STS offers many additional features over Eclipse for Spring-based applications.

  2. Unzip Spring Roo to the location of your choice.

  3. Set the environment variable:

    1. On a Windows machine, add %ROO_HOME%\bin to the path where ROO_HOME is the path to the unzipped Roo files.

    2. On a *nix machine, create a symbolic link to $ROO_HOME/bin/roo.sh (as in sudo ln -s ~/spring-roo-1.x.x/bin/roo.sh /usr/bin/roo)


Creating a ShortNotes application using Spring Roo

Now that you have Spring Roo up and running, you can start to build the application. Follow these steps to create ShortNotes application.

  1. Open your operating system command-line shell and create a directory named shortnotes using mkdir command.

  2. Go inside the shortnotes directory and type roo (or roo.sh) and press enter. This will load the Roo shell.

  3. First, create the project inside the Roo shell using the project command:

    project --topLevelPackage com.xebia.shortnotes --projectName shortnotes

    The project command will create the Spring Maven template project. The main artifacts produced by this command are pom.xml and the applicationContext.xml file. pom.xml will contain all required Spring jars and different maven plugins and repository declarations. The applicationContext.xml file is a generic Spring context file which specific components scan over the com.xebia.shortnotes package so that Spring can find all the classes annotated with @Component, @Service, and @Repository annotation. The component scan will exclude all classes annotated with @Controller annotation as these will be loaded by the web application context.

  4. Type thehint command to suggest the next logical action. It will suggest that you either do jpa setup or mongo setup. As you build a MongoDB backed application, use the mongo setup command:

    mongo setup --databaseName shortnotes --host localhost --port 27017

    The mongo setup command does not have any required attributes but for this demonstration I specified the databaseName, host, and port attributes. If you don't specify this command, Spring Roo will put default values which are the same as what you specified. You can specify three more attributes: username, password, and cloudfoundry. The username and password are used to access the MongoDB in case your datastore requires authentication information. The cloudfoundry attribute is used when you want to deploy the application to the cloudfoundry platform as a service. I will showcase it later in the article.

    The mongo setup command does three things:

    • Add required dependencies in pom.xml
    • Create a new Spring context file applicationContext-mongo.xml
    • Create the database.properties file

    The dependencies added by this command are for Spring MongoDB support and bean validation support. The database.properties file contain the properties like host, port, databaseName. The applicationContext- mongo.xml file contains the most important information. It makes use of mongo namespace which avoids writing Spring beans declarations and makes it very easy for developers. Refer to the applicationContext-mongo.xml snippet in Listing 17.

    Listing 17. Sample applicationContext-mongo.xml
    <mongo:db-factory dbname="${mongo.database}" host="${mongo.host}" -----> (1) 
    id="mongoDbFactory" port="${mongo.port}"/> 
    
    <mongo:repositories base-package="com.xebia.shortnotes"/> -------------> (2)
    
    <context:annotation-config/> ------------------------------------------> (3)
    
    <bean ----->(4) id="mongoTemplate"> 
    
        <constructor-arg ref="mongoDbFactory"/> 
    
    </bean>

    The applicationContext-mongo.xml has a small bug. The bug is that dbname corresponds to a property called mongo.database but this property does not exist in the database.properties file. Replace mongo.database with mongo.name. Look the four lines declared in the XML snippet in Listing 17.

    1. The first line creates a factory which will produce a Mongo instance for connecting to the MongoDB database with the name shortnotes, running at localhost on port 27017. This declaration can also take username and password for authentication purpose. Another important and useful attribute which is not shown in this declaration is write-concern, which I mentioned earlier in this article. It allows you to change the write behavior. The default value is NONE which is "fire and forget" and you might loose data.

    2. The second line makes sure that all of the interfaces which extend the MongoRepository interface or any of its super type in the com.xebia.shortnotes package will have a Spring repository bean. A repository implementation bean will be created dynamically and will have CRUD and pagination support. This takes a lot of work off of the developer and removes a lot of boilerplate code related to the CRUD and pagination methods. This can generate CRUD methods and finder methods as well, based on some conventions. For example, in your use case, to search for all the notebooks for an author, you can declare a method as shown below and its implementation is generated at runtime. Refer to the Spring MongoDB documentation in Resources for more details.

       public List<Notebook> findByAuthor(String author)
    3. The third line is used to translate any RuntimeException to an appropriate exception from the Spring exception hierarchy.

    4. The last line creates a MongoTemplate bean which can be used to perform create, delete, find, and update operations with various other useful methods. It also provides a mapping between your domain objects and MongoDB documents. You can do everything that you do with repositories with the MongoTemplate but you can't do vice-versa. This is the most important class of Spring MongoDB support. The methods exposed in the MongoTemplate class maps very closely with methods that you will execute on the MongoDB command line client. The MongoTemplate class makes use of Query and Criteria APIs for building queries. In the example of your use case, to search for all the notebooks for an author, enter:

      List<Notebook> myNotebooks = 
           mongoTemplate.find(Query.query(Criteria.where("author").is
                     ("shekhar")),Notebook.class)
  5. After setting up MongoDB, create the Notebook and Note entities. Use the mongo entity command in Listing 18.

    \
    Listing 18. Creating the entities
     entity mongo --class ~.domain.Notebook --testAutomatically 
     entity mongo --class ~.domain.Note --testAutomatically

    The two commands in Listing 18 create Notebook.java and Note.java along with some ITDs and test infrastructure code. The testAutomatically attribute is responsible for creating integration tests for respective domain classes. The important artifacts generated by the mongo entity command are Notebook_Roo_Mongo_Entity.aj and Note_Roo_Mongo_Entity.aj. These ITDs specify that the entities should have @Persistent annotation and the id are of type BigInteger. Spring MongoDB converts the BigInteger id to ObjectId before persisting. The rest of the artifacts are for specifying getters and setters for entity fields and the toString() method.

  6. Now that you have created entities, add some fields to them using the field command. First add fields to the Notebook entity using the commands in Listing 19.
    Listing 19. Creating the Notebook fields
    focus --class ~.domain.Notebook 
    field string --fieldName name --notNull 
    field string --fieldName author --notNull 
    field date --fieldName created --type java.util.Date --notNull

    Now add fields to the Note entity with the commands in Listing 20.

    Listing 20. Creating the Note fields
    focus --class ~.domain.Note 
    field string --fieldName title --notNull 
    field string --fieldName content --notNull --sizeMax 4000 
    field date --fieldName created --type java.util.Date --notNull

    The interesting thing about the commands in Listing 20 is that the field name created in both entities will be a non-updatable field. This means that when you scaffold a user interface for these entities you cannot edit the created field.

  7. The domain model that you created so far is not complete as you have not defined the relationship between Notebook and Note. As discussed earlier in the schema design, each note document will have an embedded notebook document. This means that if you have a Notebook with 100 Notes, you will have one original notebook document in Notebook collection and 100 other copies of notebook in 100 notes. But this schema design is simpler and better suits your requirements. To add a notebook field to a Note entity, type:

    field reference --type ~.domain.Notebook --fieldName notebook --notNull

    This command ensures that Note will only get created when there is already a Notebook.

  8. Now that your domain model is complete, you can create repositories for the entities created previously. The repositories are provided by Spring MongoDB support. To create a repository for the Notebook entity, execute the command in Listing 21. Example output is also shown.

    Listing 21. Sample output from repository creation
    repository mongo --interface ~.repository.NotebookRepository 
                   --entity ~.domain.Notebook
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository/NotebookRepository.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository/
                   NotebookRepository_Roo_Mongo_Repository.aj

    The output tells you that it created a repository folder, a Java™ file, and an ITD. The NotebookRepository.java file contains a findAll() method to find all the Notebooks and is annotated with @RooMongoRepository, which tells Roo to generate the NotebookRepository_Roo_Mongo_Repository.aj ITD file. At compile time, code from ITD will be pushed into the interface. The ITD is where all the code sits. Listing 22 shows the ITD file generated by Spring Roo.

    Listing 22. Example ITD file
    privileged aspect NotebookRepository_Roo_Mongo_Repository { 
        declare parents: NotebookRepository extends 
            PagingAndSortingRepository<Notebook, BigInteger>; 
        declare @type: NotebookRepository: @Repository; 
    }

    The ITD defines that the NoteBookRepository interface be annotated with the @Repository annotation and that the interface should extend the PagingAndSortingRepository interface. When the NotebookRepository interface is compiled it will have the @Repository annotation and will extend the PagingAndSortingRepository. As already discussed in step 4, the repository implementation will get created at runtime. As you implement the PagingAndSortingRepository interface, you will have CRUD methods and additional methods to retrieve entities using pagination and sorting.

    On the similar lines, you can create repository for Note entity:

    repository mongo --interface ~.repository.NoteRepository --entity ~.domain.Note
  9. Next, add a service layer corresponding to Notebook and Note entity. This acts as a facade between the controller layer and repository layer. You can put your application-specific business logic here. Listing 23 shows the service command for the Notebook entity and its output.

    Listing 23. Example service output for Notebook
    service --interface ~.service.NotebookService --entity ~.domain.Notebook
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookService.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookServiceImpl.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookService_Roo_Service.aj
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookServiceImpl_Roo_Service.aj

    On similar lines, you can create service layer for Note entity: service --interface ~.service.NoteService --entity ~.domain.Note

  10. Finally, you can create Spring MVC controllers for the application using web mvc commands as in Listing 24.

    Listing 24. Example web mvc commands
    web mvc setup 
    web mvc all --package ~.web

    The web mvc setup command will setup the required infrastructure for a Spring MVC application by adding Spring MVC dependencies in pom.xml, creating a web application context file called webmvc-config.xml, generating tag libraries, and setting up internationalization. The web mvc all command will create controller classes and generate corresponding views.

  11. Quit the roo shell.

  12. Start the MongoDB server using the mongod executable.

  13. To run the application, type mvn tomcat:run which will start the tomcat server. Open a web browser and go to http://localhost:8080/shortnotes/. You can create a notebook and then create a note specifying which notebook to use. For example, I created a notebook with name MongoDB Notes and author shekhar. Then I created note with title and content as Getting Started with MongoDB and assigned it the notebook we created. Listing 25 shows the Notebook document and Notes document.

    Listing 25. Sample Notebook and Notes documents
    {
        "_id" : ObjectId("4f808ee084aeed43f24b692b"),
        "_class" : "com.xebia.shortnotes.domain.Notebook", 
        "name" : "MongoDB Notes", 
        "author" : "shekhar", 
        "created" : ISODate("2012-04-07T19:00:48.386Z") 
    }
    
    {
        "_id" : ObjectId("4f808ef184aeed43f24b692c"), 
        "_class" : "com.xebia.shortnotes.domain.Note", 
        "title" : "Getting Started with MongoDB", 
        "content" : "Getting Started with MongoDB", 
        "created" : ISODate("2012-04-07T19:01:05.031Z"),
        "notebook" : { 
            "_id" : ObjectId("4f808ee084aeed43f24b692b"), 
            "name" : "MongoDB Notes", 
            "author" : "shekhar", 
            "created" : ISODate("2012-04-07T19:00:48.386Z")
        } 
    }
  14. If you update the Notebook name to MongoDB Cookbook, you will find that any Notes which have that notebook will not be updated. This is expected behavior. To fix this, update all the Notes after the Notebook is updated. Create a new method updateNotesWithNotebook in NoteServiceImpl as in Listing 26.

    Listing 26. Creating the updateNotesWithNotebook command
    @Autowired 
    MongoTemplate mongoTemplate; 
    	 
    public void updateNotesWithNoteBook(Notebook notebook){ 
        Update update = new Update().set("notebook.name", 
        notebook.getName()).set("notebook.author", notebook.getAuthor()); 
    
    Query query = Query.query(Criteria.where("notebook._id").is(new 	
        ObjectId(notebook.getId().toString(16)))); 
            mongoTemplate.updateMulti(query, update, Note.class);

    The code in Listing 26 uses the updateMulti method of MongoDBTemplate to update all the documents which correspond to the updated Notebook. The updateMulti() method takes three arguments: query object, update object, and collection entity type. The query object specifies to select all the Note documents which have Notebooks with the given id. Then you create an update object to set the author and name of Notebook.

    After creating the method, you need to call this after you update the Notebook in NotebookController (see Listing 27). Make sure that you push the update method from ITD to the NotebookController java class.

    Listing 27. Example of pushing update from ITD
    @Autowired 
    private NoteService noteService; 
    
    @RequestMapping(method = RequestMethod.PUT, produces = "text/html") 
    public String update(@Valid Notebook notebook, BindingResult bindingResult, 
    Model uiModel, HttpServletRequest httpServletRequest) { 
    
            if (bindingResult.hasErrors()) { 
                populateEditForm(uiModel, notebook); 
                return "notebooks/update"; 
            } 
            uiModel.asMap().clear(); 
            notebookService.updateNotebook(notebook); 
            noteService.updateNotesWithNoteBook(notebook); 
            return "redirect:/notebooks/" + 	
                encodeUrlPathSegment(notebook.getId().toString(), httpServletRequest); 
     }
  15. You will face the same problem when a Notebook is deleted as Notes will still have references to the Notebook. It makes sense to remove all the Notes for a particular Notebook if it is deleted. Add the removeNotes() method in the NoteServiceImpl class as in Listing 28.

    Listing 28. Example of removeNotes() method
    public void removeNotes(Notebook notebook){ 
        mongoTemplate.remove(Query.query(Criteria.where("notebook._id").is(new
            ObjectId(notebook.getId().toString(16)))), Note.class);
    }

    The removeNotes() method is called when Notebook is deleted. Add the call to removeNotes in NotebookController the delete() method as in Listing 29.

    Listing 29. Adding removeNotes to NotebookController
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE,
            produces = "text/html")
    public String delete(@PathVariable("id") BigInteger id,
            @RequestParam(value = "page", required = false) Integer page,
            @RequestParam(value = "size", required = false) Integer size,
            Model uiModel) {
    Notebook notebook = notebookService.findNotebook(id);
    
    noteService.removeNotes(notebook);
    notebookService.deleteNotebook(notebook);
    uiModel.asMap().clear();
    uiModel.addAttribute("page", (page == null) ? "1" :
        page.toString());
    uiModel.addAttribute("size", (size == null) ? "10" :
        size.toString());
    return "redirect:/notebooks";
    }

    You can run the application again using mvn tomcat:run and create a notebook, then create notes, and then update notebook. You will see that the notes also have an updated notebook document. You can also try deleting notebook and all the notes for the notebook are also deleted.

Deploying to Cloud Foundry

Now that you have created the shortnotes application, it makes sense to deploy it. Spring applications can be deployed to the Cloud Foundry public cloud without any modifications. I covered Cloud Foundry in detail in Part 4 and Part 6. Refer to those articles if it is unfamiliar to you. In this article, you will use a vmc ruby gem to deploy the conference application on Cloud Foundry. Follow these steps:

  1. Fire up the roo shell again as you need to run the mongo setup command again. This time you will switch to the cloud foundry MongoDB instance. Execute the command shown below on Roo shell. This will update the applicationContext-mongo.xml.

    mongo setup --cloudFoundry
  2. Install the vmc client on your machine. (For a step-by-step-tutorial on how to install the command-line interface, see Resources.)

  3. Login to the Cloud Foundry public cloud using the credentials you registered at Cloud Foundry. Type the vmc login command and it will ask for your email and password as in Listing 30.

    Listing 30. Logging into Cloud Foundry through vmc
    shekhar@shekhar:~/dev/writing/shortnotes/target$ vmc login 
    Attempting login to [http://api.cloudfoundry.com] 
    Email: shekhargulati84@gmail.com 
    Password: ************* 
    Successfully logged into [http://api.cloudfoundry.com]
  4. Once you install the vmc client, do the maven build of the shortnotes application. Type mvn clean install

  5. After you build the project, push the application to Cloud Foundry. To push the code from the command line, navigate to the target folder and type the vmc push command. The vmc push command will ask questions. Respond to them as in Listing 31.

    Listing 31. Pushing the application to Cloud Foundry with vmc
    shekhar@shekhar:~/dev/writing/dw/shortnotes/target$ vmc push 
    Would you like to deploy from the current directory? [Yn]: Y 
    Application Name: shortnotes 
    Application Deployed URL [shortnotes.cloudfoundry.com]: 
    Detected a Java SpringSource Spring Application, is this correct? [Yn]: Y 
    Memory Reservation (64M, 128M, 256M, 512M, 1G) [512M]: 
    Creating Application: OK 
    Would you like to bind any services to 'shortnotes'? [yN]: y 
    Would you like to use an existing provisioned service? [yN]: N 
    The following system services are available 
    1: mongodb 
    2: mysql 
    3: postgresql 
    4: rabbitmq 
    5: redis 
    Please select one you wish to provision: 1 
    Specify the name of the service [mongodb-484f6]: 
    Creating Service: OK 
    Binding Service [mongodb-484f6]: OK 
    Uploading Application: 
      Checking for available resources: OK 
      Processing resources: OK 
      Packing application: OK 
      Uploading (85K): OK   
    Push Status: OK 
    Staging Application: OK                                                         
    Starting Application: OK
  6. Finally you will see the shortnotes application running in cloud at http://shortnotes.cloudfoundry.com/

To get the source code of the shortnotes application, clone the shortnotes github repository (see Resources).


Conclusion

This article introduced you to MongoDB and how to build Spring MongoDB applications using Spring Roo. You covered the basics of MongoDB, choosing the schema to best fit your requirements, and how Spring Roo helps in quickly bootstrapping Spring MongoDB applications.

In this article, I have not covered features like MongoDB MapReduce, array modifiers, setting up MongoDB replication, geospatial querying, and others. There is plenty more to explore. You'll find good resources in the documentation listed in Resources.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology, Linux
ArticleID=833305
ArticleTitle=Introducing Spring Roo, Part 7: Develop Spring MongoDB applications using Spring Roo
publish-date=09072012