Sunday, August 24, 2014

Spring Data with MongoDB

MongoDB is an open source document database and one of most popular NoSQL databases. See mongdb official page for more details.
Spring data has an integration with the MongoDB.

How to use

Add the following dependencies to use with maven.
  • Spring-data-mongodb
  • mongodb java driver
  • Spring context related modules (As spring requires these)
  <!-- Spring framework -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>3.2.2.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>3.2.2.RELEASE</version>
  </dependency>

  <!-- mongodb java driver -->
  <dependency>
   <groupId>org.mongodb</groupId>
   <artifactId>mongo-java-driver</artifactId>
   <version>2.11.0</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-mongodb</artifactId>
   <version>1.5.2.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>2.2.2</version>
  </dependency>

Configuration

Configuration can be done in two ways a) Using @Configuration and b) Using Spring XML

a) Using Configuration

Create a class annotated with Configuration and add two methods : mongoDbFactory() and mongoTemplate() like this.
package com.test.mongodb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;

import com.mongodb.MongoClient;

@Configuration
public class SpringMongoConfig
{
    public @Bean
    MongoDbFactory mongoDbFactory() throws Exception
    {
        return new SimpleMongoDbFactory(new MongoClient(), "sample");
    }

    public @Bean
    MongoTemplate mongoTemplate() throws Exception
    {

        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory());

        return mongoTemplate;

    }
}

How to create context

Call the above configuration using : AnnotationConfigApplicationContext
    AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(SpringMongoConfig.class);
    MongoOperations mongoOperation = (MongoOperations)ctx.getBean("mongoTemplate");

b) Using Spring XML File

See the following spring-config.xml file to configure mongo DB template
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:mongo="http://www.springframework.org/schema/data/mongo"
 xsi:schemaLocation="http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context-3.0.xsd
          http://www.springframework.org/schema/data/mongo
          http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <mongo:mongo host="127.0.0.1" port="27017" />
 <mongo:db-factory dbname="sample" />

 <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
 </bean>

</beans>

How to create context

Create the application context using spring-config.xml as follows:
     AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
     MongoOperations mongoOperation = (MongoOperations) ctx.getBean("mongoTemplate");     
Any one of the options (a) and (b) is enough. XML configuration is always preferable.

MongoDB Document

Map the MongoDB Document using a class annotated using @Document annotation
package com.test.mongodb.entity;

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

@Document(collection = "person")
public class Person
{
    @Id
    private String id;
    private String name;
    private int age;
    private double salary;
    public String getId()
    {
        return id;
    }
    public void setId(String id)
    {
        this.id = id;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age = age;
    }
    public double getSalary()
    {
        return salary;
    }
    public void setSalary(double salary)
    {
        this.salary = salary;
    }
    @Override
    public String toString()
    {
        String str = "Person details : [id:%s, name:%s ,age:%d, salary:%f]";
        return String.format(str, id,name,age,salary);
    }
}

Operations

Operations on the MongoDB Document can be performed using using mongoOperation object.

Application

The application class will be as follows
        Person p = new Person();
        p.setId("10");
        p.setName("Joe");
        p.setAge(30);
        p.setSalary(3000.00);
        mongoOperation.save(p);
        
        //Create Query for fetching the data
        Query sQuery = new Query(Criteria.where("salary").lte(3000.00));
        Person person = mongoOperation.findOne(sQuery, Person.class);
        if(person != null)
            System.out.println("After inserting : Found Person : " + person);
        else
            System.out.println("No person found");
        
        mongoOperation.updateMulti(sQuery, 
                Update.update("salary", "40000"),Person.class);
        
        List<Person> list = mongoOperation.findAll(Person.class);
        System.out.println("After updating Size is "+(list == null ? 0: list.size()));
        if(list != null)
        {
            for(Person pr : list)
            {
                System.out.println(pr);
            }
        }
        
        Query rQuery = new Query(Criteria.where("id").is("10"));
        mongoOperation.remove(rQuery, Person.class);
        
        list = mongoOperation.findAll(Person.class);
        System.out.println("After deleteing one Person. Size is "+(list == null ? 0: list.size()));
        if(list != null)
        {
            for(Person pr : list)
            {
                System.out.println(pr);
            }
        }
        ctx.close();
Output of the above program will be :
After inserting : Found Person : Person details : [id:10, name:Joe ,age:30, salary:3000.000000]
After updating Size is 2
Person details : [id:2, name:Joe ,age:30, salary:40000.000000]
Person details : [id:10, name:Joe ,age:30, salary:40000.000000]
After deleteing one Person. Size is 1
Person details : [id:2, name:Joe ,age:30, salary:40000.000000]
Happy Learning!!!!

Sunday, August 3, 2014

Google Guava CacheBuilder

This cache is mainly used where

  • Where the same data is retrieved multiple times
  • Where the time required to access the data to be small
  • Cache size is limited and known

How to create

Cache to be created with or without CacheLoader. We will see only with CacheLoader. LoadingCache is the Cache implementation that can be created using CacheBuilder and add some properties to it and include CacheLoader to it.
         LoadingCache<String, Person> persons = CacheBuilder.newBuilder() 
                  .initialCapacity(30) 
                  .maximumSize(40) 
                  .recordStats() 
                  .build(loader);
Here Person is the object stored in Cache using key of type String. loader is the CacheLoader. See below on how to create
        CacheLoader loader = new CacheLoader()
        {
            public Person load(String key) throws Exception
            {
                return getPerson(key);
            }
        };
Loader has to be created with load method implemented with the basic operation on on how to load the object using the key.

CacheBuilder

CacheBuilder defines the properties of the cache like

  • Initial capacity: Initial capacity of the cache
  • Maximum size: The maximum size of the cache and the cache evicts the object before the size is reached.
  • Expire after access: Cache automatically removes the entry once the time is elapsed after the last access.
  • Expire after write: Cache automatically removes the entry once the time is elapsed after the last write.
  • Refresh after write: Cache retrieves the data and refreshes once the time is elapsed using load.
  • Record stats : Once this is called, the stats will be recorded on the cache and returns the status when called stats() method.

How to create CacheBuilder

Calling methods explicitly

Call each of the following methods on the CacheBuilder to set the properties 
LoadingCache<String, Person> persons = CacheBuilder.newBuilder()
                .initialCapacity(80)
                .maximumSize(20)
                .refreshAfterWrite(20, TimeUnit.HOURS)
                .expireAfterAccess(1, TimeUnit.DAYS)
                .recordStats()
                .build(loader);

Using CacheBuilderSpec

Set all the parameters in a string comma delimited in CacheBuilderSpec and pass to CacheBuilder.from(spec) to build the cache with the parameters
CacheBuilderSpec spec = CacheBuilderSpec
                .parse("initialCapacity=10,maximumSize=20,refreshInterval=20000s");
        LoadingCache<String, Person> p2 = CacheBuilder.from(spec).build(loader);

How to Access

Accessing the objects in the cache are simple using get and put methods as a HashMap.

Sample Code


        Person p = new Person();
        p.setName("Joe");
        p.setLocation("New Yrok");
        persons.put("P1", p);

        System.out.println("Size of Cache is : " + persons.size());

        Person p1 = new Person();
        p1.setName("Joy");
        p1.setLocation("New Jersy");
        persons.put("P2", p1);

        System.out.println("Size of Cache is : " + persons.size());

        Person d1 = persons.get("P1");
        System.out.println("Person is : " + d1.getName());

        System.out.println("Size of Cache is : " + persons.size());

Output will be

Size of Cache is : 1
Size of Cache is : 2
Person is : Joe
Size of Cache is : 2

Evict an object

There are different ways of evict options
Timed Eviction: Use the methods expireAfterAccess and expireAfterWrite to evict automatically after the time elapsed
Explicit Eviction:  Use the method invalidate(String) to evict one object with the given key and use invalidateAll to remove all.

Other Features

There are other important features that we can make use of when required.

Cache Stats

The cache can record stats when we explicitly call recordStats (Default is off). Once switched on, the stats() method returns the status like
  • Load Count
  • Load Exception Count
  • Load Success Count
  • Eviction Count
  • Hit rate
  • Miss rate etc.

asMap

The complete cache can be returned as map with key and values.

No InterruptedException

The cache doesn't throw InterruptedException but they are designed to make it throw. (But the documentation says (We could have designed these methods to support InterruptedException, but our support would have been incomplete, forcing its costs on all users but its benefits on only some.)

Happy Learning!!!