Open source Java projects: Spring Data

Use common Spring queries to access multiple NoSQL data stores

Spring Data provides the boilerplate code and plumbing to enable you to interact with various NoSQL repositories in a Spring-consistent manner. Depending on your needs, you could even find the persistence logic for your entire application defined in a handful of Spring Data interfaces. Get started with Spring Data domain objects and repositories, then learn about two ways to implement Spring queries in Spring Data: by naming convention or using QueryDSL, which ensures type-safe queries that are validated at compile time.

Reflect back on all the persistence code that you've written for Java applications. Given the requirements, can you say that you know every line of code that you need to write? If your answer is yes, and you're just putting off writing the code, then I believe you're better off automating that process. In fact, years ago I built a Maven plug-in (see Resources) that processed a class diagram in XMI format and generated a domain model. The domain model was annotated with Hibernate annotations and included a set of persistence classes. When it came to building a MongoDB persistence strategy I almost fell back on that approach -- but that was before I discovered Spring Data.

Spring Data and Neo4j

One thing that the Spring framework has done very well is to reduce or eliminate boilerplate code. Instead of spending time on generic implementation code, Spring developers spend more time on business logic. Spring provides the configuration and plumbing code.

At SpringOne in 2010, Spring creator Rod Johnson and Neo4j co-founder Emil Eifrem were trying to determine the best way to integrate support for Neo4J into the Spring framework. Their collaboration eventually led to the Neo4J module for Spring Data. Thus Spring Data evolved into an abstraction layer on top of data repositories. Initially focused on NoSQL, it was later extended for JPA and relational databases.

Get started with Spring

If you're unfamiliar with Spring's programming model, start here with Steve's introductory tutorial, "Mastering Spring MVC" (JavaWorld, April 2009).

It's good to keep Spring Data's inception in mind, because NoSQL repositories are quite different from relational databases. Relational databases provide support for SQL queries and solve a specific category of problems, which makes them easy to abstract. But NoSQL repositories specialize in particular features and functionality. MongoDB, for instance, is good at managing dynamic documents; Neo4J is good at representing highly interconnected graphed entities; and Redis is good at representing key/value entries in a cache.

Each NoSQL repository addresses a different problem domain, and each problem domain prefers a specific query style. If Spring Data were to simply abstract all NoSQL repositories you would lose the unique benefits of the individual repositories. So instead Spring Data provides a consistent programming model for interacting with NoSQL repositories, using patterns and models from the Spring framework. As a result you get a consistent way of interacting with different NoSQL repositories, and you're able to leverage each one's individual strengths.

This Open source Java projects profile walks through an example implementation using Spring Data to manage domain-model persistence to MongoDB. I'll start with an overview of state-of-the-art persistence before Spring Data, then we'll dig into Spring Data's architecture, especially its handling of repositories and domain objects. Finally, I'll show you a couple of ways to write queries against your Spring Data domain objects. We'll conclude with a JUnit test case that demonstrates how to use Spring Data to persist data to and from a running MongoDB instance.

MongoDB data persistence without Spring Data

If you're not using Spring Data, then you are at the disposal of a NoSQL data provider's API. For MongoDB, the persistence code would look like what you see in Listing 1.

Listing 1. Accessing MongoDB using the MongoDB API

 // Create a Mongo instance
    Mongo mongo = new Mongo( "localhost" );

    // Get a DB instance to the "informit" database
    DB db = mongo.getDB( "test" );

        // Show all collections in the database
        Set collectionNames = db.getCollectionNames();
        for( String collection : collectionNames )
        {
            System.out.println( "Collection: " + collection );
        }
        Assert.assertNotNull( "Could not obtain a database connection," db );
        
        // Create a new collection
        DBCollection collection = db.createCollection( "users," null );

        // Add something to the collection (simple)
        BasicDBObject doc = new BasicDBObject();
        doc.put( "firstName," "Steven" );
        doc.put( "lastName," "Haines" );
        doc.put( "age," 39 );
        collection.insert( doc );

        // Add another record using a builder
        BasicDBObjectBuilder builder = BasicDBObjectBuilder.start();
    builder.add( "firstName" ,"Michael" );
    builder.add( "lastName" ,"Haines" );
    builder.add( "age," 9 );
        collection.insert( builder.get() );

        // Find all objects
        DBCursor cursor = collection.find();
        while( cursor.hasNext() )
        {
            System.out.println( cursor.next() );
        }

        // Find users with the last name Haines
        builder = BasicDBObjectBuilder.start();
        builder.add( "lastName," "Haines" );
        cursor = collection.find( builder.get() );
        System.out.println( "Users with last name of Haines: " );
        while( cursor.hasNext() )
        {
            System.out.println( cursor.next() );
        }

        // Find users under 10
        builder = BasicDBObjectBuilder.start();
        builder.add( "age," new BasicDBObject( "$lt," 10 ) );
        cursor = collection.find( builder.get() );
        System.out.println( "Find all documents with an age less than 10: " );
        while( cursor.hasNext() )
        {
            System.out.println( cursor.next() );
        }

        // Clean up
        builder = BasicDBObjectBuilder.start();
        builder.add( "lastName," "Haines" );
        collection.remove( builder.get() );

        // Find our objects
        System.out.println( "After delete:" );
        cursor = collection.find();
        while( cursor.hasNext() )
        {
            System.out.println( cursor.next() );
        }

Note: The code in Listing 1 originally appeared in the March 2003 InformIT article "Accessing MongoDB from Java."

In this type of persistence implementation you would first create a Mongo instance to connect to the required host and port and then obtain a DB object referencing the instance that you wanted to interact with. The DB would enable you to access collections, which are similar to SQL database tables. You would then create a new collection named users, insert a couple of users, and then exercise some basic query operations. Invoking find(), for instance, would return a cursor referencing all the objects in the collection.

You could refine your queries by configuring a BasicDBObjectBuilder with additional criteria, such as the last name of all users in the database. Upon retrieving a BasicDBObject from the builder you could pass it to the find() method in order to only receive users with a specified last name (like "Haines" in Listing 1). Finally, calling remove() would delete the recently added users from the collection.

The code in Listing 1 isn't necessarily complex, but after writing it I decided to compile it all together into a MongoDB template class -- a project that you can read about here. Spring Data did not exist at the time, but Spring provides several implementations of the Template design pattern, such as JdbcTemplate and RestTemplate, which is now modeled by the new Spring Data MongoTemplate. I have found templates far easier to use than native APIs, so it was a natural progression to using a MongoDB template, as shown in Listing 2.

Listing 2. Using a custom MongoDBTemplate

 public void addUser( final User user )
    {
        // Delegate the insertion operation to the MongoDBTemplate
        template.insert( "users", new MongoDBDBObjectBuilder() {
            @Override
            public void build( BasicDBObjectBuilder builder ) {
                builder.add( "firstName", user.getFirstName() );
                builder.add( "lastName", user.getLastName() );
                builder.add( "age", user.getAge() );
            }
        });
    }

    @Override
    public List<User> findAll()
    {
        return template.findAll( "users", userBuilder );
    }

    @Override
    public List<User> findByLastName( final String lastName )
    {
        return template.find( "users",
            new MongoDBQueryCriteriaBuilder() {
                @Override
                public void build( BasicDBObjectBuilder builder ) {
                    // Add the last name criteria
                    builder.add( "lastName", lastName );
                }
            },
            userBuilder
        );
    }
    
    @Override
    public List<User> findByAgeGreaterThan( final int age )
    {
        return template.find( "users",
            new MongoDBQueryCriteriaBuilder() {
                @Override
                public void build( BasicDBObjectBuilder builder ) {
                    // Add the age criteria
                    builder.add( "age", new BasicDBObject( "$gt", age ) );
                }
            },
            userBuilder
        );
    }  

The template code in Listing 2 greatly simplifies the manual implementation from Listing 1, but it still contains boilerplate code that would be better handled by a framework like Spring Data. Next we'll look at some Spring Data repositories that simplify NoSQL and MongoDB access.

Building a Spring Data repository

The repository is a core concept in Spring Data. If you've written code to access a database through a persistence library like Hibernate, then the idea of a repository should be nothing new. But I think you'll find using a Spring Data repository far simpler than using a standard Spring repository.

The process of building a Spring Data repository is as follows:

  1. Define the domain object you want to persist
  2. Extend a Spring Data interface with one that contains your persistence methods
  3. Configure the Spring framework to scan your source code, find repositories, and generate persistence code
  4. Wire the repository into the class that will use it

We'll start with defining the domain object and extending Spring Data interfaces to create two different repositories, then we'll move on to configuring queries in Spring. Finally, in the example application at the end, we'll configure Spring and wire the repositories into a Spring service.

Define a domain object

Domain objects are simple POJOs that expose fields to be persisted to and from the data store. In Listing 3, I've annotated my domain object with the org.springframework.data.mongodb.core.mapping.Document annotation, but aside from the annotation what you're looking at is just a JavaBean.

Listing 3. Domain object: User.java

package com.geekcap.javaworld.springdata.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;

/**
 * Represents a user in the application
 */
@Document
public class User
{
    @Id
    private String id;
    private String firstName;
    private String lastName;
    private String emailAddress;
    private int age;

    @DBRef
    private List<Address> addresses;

    public User() {
    }

    public User(String firstName, String lastName, String emailAddress) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailAddress = emailAddress;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }
}

As shown in Listing 3, Spring Data's domain objects are POJOs that contain a set of persistent fields. Note that the id is annotated with the @Id annotation, which is specific to MongoDB, and the Address list is annotated by the @DBRef annotation. The @DBRef annotation creates a field in the User collection named addresses, which contains an array of DBRef instances.

After inserting a User into MongoDB, the user, with the address reference, would look like so:

1 2 3 4 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more