Open source Java projects: Spring Data

Use common Spring queries to access multiple NoSQL data stores

1 2 3 4 5 Page 4
Page 4 of 5

Listing 12. UserServiceImpl.java

package com.geekcap.javaworld.springdata.service;

import com.geekcap.javaworld.springdata.model.Address;
import com.geekcap.javaworld.springdata.model.QUser;
import com.geekcap.javaworld.springdata.model.User;
import com.geekcap.javaworld.springdata.repository.AddressRepository;
import com.geekcap.javaworld.springdata.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * Implementation of the UserService
 */
@Service
public class UserServiceImpl implements UserService
{
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private AddressRepository addressRepository;

    @Override
    public void addUser( User user )
    {
        // See if we need to add addresses
        if( user.getAddresses() != null )
        {
            for( Address address : user.getAddresses() )
            {
                Address createdAddress = addressRepository.save( address );
                address.setId( createdAddress.getId() );
            }
        }

        userRepository.save( user );
    }

    @Override
    public User getUser(String id)
    {
        return userRepository.findOne( id );
    }

    @Override
    public User findUserByEmailAddress(String emailAddress)
    {
        return userRepository.findByEmailAddress( emailAddress );
    }

    @Override
    public List<User> findUsersByLastName( String lastName )
    {
        return userRepository.findByLastName( lastName );
    }


    @Override
    public List<User> findUsers()
    {
        List<User> users = new ArrayList<User>();
        for( User user : userRepository.findAll() )
        {
            users.add( user );
        }
        return users;
    }

    @Override
    public List<User> findUsersSortByAge( SortOrder sortOrder )
    {
        // Create the Sort object
        Sort sort = null;
        if( sortOrder == SortOrder.ASCENDING )
        {
            sort = new Sort( Sort.Direction.ASC, "age" );
        }
        else
        {
            sort = new Sort( Sort.Direction.DESC, "age" );
        }

        // Query for all users, sorted by age
        List<User> users = new ArrayList<User>();
        for( User user : userRepository.findAll( sort ) )
        {
            users.add( user );
        }
        return users;

    }

    @Override
    public List<User> findUsersByAgeOver( int age )
    {
        return userRepository.findByAgeOver( age );
    }

    @Override
    public List<User> findUsersWithAgeBetween( int lower, int upper )
    {
        return userRepository.findByAgeBetween( lower, upper );
    }

    @Override
    public List<User> findByLastNameOverAge( String lastName, int age )
    {
        QUser user = new QUser( "user" );
        List<User> users = new ArrayList<User>();
        for( User u : userRepository.findAll( user.lastName.contains( lastName ).and( user.age.gt( age ) ) ) )
        {
            users.add( u );
        }
        return users;
    }

    @Override
    public List<User> findUsers( int pageNumber, int pageSize )
    {
        // Create a Pageable object for the requested page number and page size
        Pageable pageable = new PageRequest( pageNumber, pageSize );

        // Retrieve a page of users
        Page<User> page = userRepository.findAll( pageable );

        // Returns the list of users
        return page.getContent();
    }

    @Override
    public void removeUser(String id)
    {
        userRepository.delete( id );
    }
}

The UserRepository and AddressRepository instances are wired-in using the @Autowired annotation. Most methods simply pass through to one of the repository methods. The findUsersSortByAge() method demonstrates how to build and use a Sort object to sort returned users by age. The findUsers(pageNumber,pageSize) method demonstrates how to page query results using a PageRequest object. Finally, the findByLastNameOverAge() method demonstrates how to use QueryDSL by creating a QUser object and using both a String and a number comparison.

The only other method that deserves explanation is the addUser() method. Because a relationship is defined between the User class and its list of Address instances, we cannot simply save the User with the addresses. Instead, we have to save the Address objects individually (the save() call returns a copy of the created Address) and update the Address's ID before saving the User.

JUnit demo

Listing 13 presents a JUnit class that demonstrates how to exercise our UserService and its corresponding repositories.

Listing 13. UserServiceTest.java

package com.geekcap.javaworld.springdata.service;

import com.geekcap.javaworld.springdata.model.Address;
import com.geekcap.javaworld.springdata.model.User;
import com.geekcap.javaworld.springdata.repository.AddressRepository;
import com.geekcap.javaworld.springdata.repository.UserRepository;
import org.junit.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.List;

/**
 * "Unit Test" for the user service; in quotes because this is more of an integration test than a unit test because
 * we're calling a real repository and talking to a real MongoDB instance
 */
public class UserServiceImplTest
{
    private static ApplicationContext applicationContext;
    private static UserService userService;
    private static UserRepository userRepository;
    private static AddressRepository addressRepository;

    @BeforeClass
    public static void beforeClass()
    {
        // Load our application context
        applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );

        // Load the user repository for manually changing the user records
        userRepository = ( UserRepository )applicationContext.getBean( "userRepository" );
        addressRepository = ( AddressRepository )applicationContext.getBean( "addressRepository" );

        // Load the UserServiceImpl service
        userService = ( UserService )applicationContext.getBean( "userServiceImpl" );
    }

    @AfterClass
    public static void afterClass()
    {
        // Delete test users
        userRepository.deleteAll();
    }

    @Before
    public void before()
    {
        userRepository.deleteAll();
    }

    @Test
    public void testAddUser()
    {
        // Create a user
        User user = new User( "Steven", "Haines", "steve@geekcap.com", 41 );

        // Insert it into the repository
        userService.addUser( user );

        // Check to see if its there
        User repositoryUser = userService.findUserByEmailAddress( "steve@geekcap.com" );
        Assert.assertNotNull( repositoryUser );
        Assert.assertEquals( "The user's first name is not correct", "Steven", repositoryUser.getFirstName() );
        Assert.assertEquals( "The user's last name is not correct", "Haines", repositoryUser.getLastName() );

        // Remove the user
        userService.removeUser( repositoryUser.getId() );
    }

    @Test
    public void testAddUserWithAddress()
    {
        // Create a user
        User user = new User( "Steven", "Haines", "steve@geekcap.com", 41 );
        Address address = new Address( "123 Some Street", "", "My City", "CA", "90210" );
        List<Address> addresses = new ArrayList<Address>();
        addresses.add( address );
        user.setAddresses( addresses );

        // Insert it into the repository
        userService.addUser( user );

        // Check to see if its there
        User repositoryUser = userService.findUserByEmailAddress( "steve@geekcap.com" );
        Assert.assertNotNull( repositoryUser );
        Assert.assertEquals( "The user's first name is not correct", "Steven", repositoryUser.getFirstName() );
        Assert.assertEquals( "The user's last name is not correct", "Haines", repositoryUser.getLastName() );
        Assert.assertNotNull( "Address is null", repositoryUser.getAddresses() );
        Assert.assertEquals( "The wrong number of addresses", 1, repositoryUser.getAddresses().size() );

        Address repositoryAddress = repositoryUser.getAddresses().get( 0 );
        Assert.assertEquals( "Street 1 is wrong", "123 Some Street", repositoryAddress.getStreet1() );
        Assert.assertEquals( "Street 2 is wrong", "", repositoryAddress.getStreet2() );
        Assert.assertEquals( "City is wrong", "My City", repositoryAddress.getCity() );
        Assert.assertEquals( "State is wrong", "CA", repositoryAddress.getState() );
        Assert.assertEquals( "Zipcode is wrong", "90210", repositoryAddress.getZipcode() );

        // Remove the user
        userService.removeUser( repositoryUser.getId() );
    }


    @Test
    public void testFindAllUsers()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            userService.addUser( new User( "Test", "User", "test" + i + "@test.com", 18 ) );
        }

        // Query for all users
        List<User> users = userService.findUsers();
        Assert.assertNotNull( "The user list was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 10, users.size() );
    }

    @Test
    public void testFindUsersNByLastName()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            String lastName = "Smith";
            if( i%2 == 0 )
            {
                lastName = "Jones";
            }
            userService.addUser(new User("Test", lastName, "test" + i + "@test.com", 18));
        }

        // Query for all users
        List<User> users = userService.findUsersByLastName( "Jones" );
        Assert.assertNotNull( "The user list was null", users );
        Assert.assertEquals( "findUsersByLastName() did not return the correct number of users with the last name of Jones", 5, users.size() );
    }

    @Test
    public void testPagedFindAllUsers()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            userService.addUser(new User("Test", "User", "test" + i + "@test.com", 18));
        }

        // Query for all users
        List<User> users = userService.findUsers( 0, 5 );
        Assert.assertNotNull( "The first page of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 5, users.size() );

        users = userService.findUsers( 1, 5 );
        Assert.assertNotNull( "The second page of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 5, users.size() );

        users = userService.findUsers( 2, 5 );
        Assert.assertNotNull( "The third page of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 0, users.size() );
    }

    @Test
    public void testFindAllUsersSortByAgeAscending()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            userService.addUser( new User( "Test", "User", "test" + i + "@test.com", 18+i ) );
        }

        // Query for all users
        List<User> users = userService.findUsersSortByAge( UserService.SortOrder.ASCENDING );
        Assert.assertNotNull( "The list of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 10, users.size() );

        int lastAge = 0;
        for( User user : users )
        {
            Assert.assertTrue( "User is not in age ascending order", lastAge < user.getAge() );
            lastAge = user.getAge();
        }
    }

    @Test
    public void testFindAllUsersSortByAgeDescending()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            userService.addUser( new User( "Test", "User", "test" + i + "@test.com", 18+i ) );
        }

        // Query for all users
        List<User> users = userService.findUsersSortByAge( UserService.SortOrder.DESCENDING );
        Assert.assertNotNull( "The list of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 10, users.size() );

        int lastAge = 100;
        for( User user : users )
        {
            Assert.assertTrue( "User is not in age ascending order", lastAge > user.getAge() );
            lastAge = user.getAge();
        }
    }

    @Test
    public void testFindUsersOverAge18()
    {
        // Add 10 users
        for( int i=0; i<10; i++ )
        {
            userService.addUser( new User( "Test", "User", "test" + i + "@test.com", 13+i ) );
        }

        // Query for all users
        List<User> users = userService.findUsersByAgeOver( 18 );
        Assert.assertNotNull( "The list of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 5, users.size() );

        for( User user : users )
        {
            Assert.assertTrue( "Age is not over 18", user.getAge() >= 18 );
        }
    }

    @Test
    public void testFindUsersBetween18and35()
    {
        // Add 10 users
        for( int i=0; i<30; i++ )
        {
            userService.addUser( new User( "Test", "User", "test" + i + "@test.com", 15+i ) );
        }

        // Query for all users
        List<User> users = userService.findUsersWithAgeBetween( 18, 35 );
        Assert.assertNotNull( "The list of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 18, users.size() );

        for( User user : users )
        {
            Assert.assertTrue( "Age is not between 18 and 35", ( user.getAge() >= 18 && user.getAge() <= 35 ) );
        }
    }

    @Test
    public void testFindUsersByLastNameOverAge()
    {
        // Add 10 users
        for( int i=0; i<30; i++ )
        {
            String lastName = "User";
            if( i%2 == 0 )
            {
                lastName = "Search";
            }

            userService.addUser( new User( "Test", lastName, "test" + i + "@test.com", 15+i ) );
        }

        // Query for all users
        List<User> users = userService.findByLastNameOverAge( "Search", 25 );
        Assert.assertNotNull( "The list of users was null", users );
        Assert.assertEquals( "findUsers() did not return the correct number of users", 9, users.size() );

        for( User user : users )
        {
            Assert.assertEquals( "Last name is not correct", "Search", user.getLastName() );
            Assert.assertTrue( "Age is not over 25", user.getAge() > 25 );
        }
    }
}

Listing 13 is pretty well documented. When you review it, be sure that you understand what it is testing and how it is testing it. If you want to add additional queries to the UserRepository, you can use this test class to validate those queries. Also note that you can obtain a reference to the UserRepository itself to test your queries, rather than having to wrap the call with the UserService. See the code in the beforeClass() method for a demonstration.

In conclusion

Spring Data provides an abstraction layer on top of multiple back-end data stores, which include JPA-based SQL stores as well as NoSQL stores like MongoDB and Neo4j. The beauty of using Spring Data is that you define interfaces that contain the queries you want to execute and Spring Data generates the implementations of those methods for you.

While Spring Data abstracts the mechanism for accessing data stores, the details of accessing a specific data store differ between data store implementations. So the Spring Data code that you write to access MongoDB will be different from the code you'd write to access Neo4j. Rather than providing support for the lowest common denominator across data stores, and hence losing the power that each data store provides, Spring Data provides a standard programming model for accessing the features of each data store, giving you the best of all worlds.

Steven Haines is a technical architect at Piksel, currently working onsite at Disney in Orlando. He is the founder of geekcap.com, an online education website, and has written hundreds of Java-related articles as well as three books: Java 2 From Scratch, Java 2 Primer Plus, and Pro Java EE Performance Management and Optimization. He lives with his wife and two children in Apopka, Florida.
1 2 3 4 5 Page 4
Page 4 of 5