Sunday, June 19, 2011

JPA service generalization, templeting

JPA made small revolution among ORM tools and EJB 2 world. Easy to use, simple unified way to develop you model and services. After i used it in few projects, i have found that all JPA services are very similar, so this is a simple try to generalize or template them.

This can be for example simple abstract interface for all:
package test.service;

import java.util.List;

public abstract interface JpaService<T> {
 void save(T t);
 T find(Long id);
 void remove(Long id);
 List<T> findAll();
 long count();
}

And abstract implementation class look like this:
package test.service.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;

import test.service.JpaService;


public abstract class JpaServiceImpl<T> implements JpaService<T>{
 /** Logger. */
   private static final Logger log = Logger.getLogger(JpaServiceImpl.class);

   /** Entity manager. */
   @PersistenceContext
   protected EntityManager em;
   
   /** Reflection util */
   protected ReflectionUtil reflectionUtil = ReflectionUtil.get();
   
   /**
    * Save entity.
    */
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT)
   public void save(T t) {
     log.debug("JpaServiceImpl-Save");
     if (reflectionUtil.getIdValue(t) == null) {
       // new
       log.debug("New");
       em.persist(t);
     } else {
       // update
       log.debug("Update");
       em.merge(t);
     }
     
   }

   /**
    * Remove entity.
    */
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED)
   public void remove(Long id) {
     log.debug("remove: " + id);
     T t = (T) find(id);
     if (t != null) {
       log.debug("found try to remove");
       em.remove(t);
       log.debug("removed");
     }
   }

   /**
    * Find entity.
    * 
    */
   @SuppressWarnings( { "unchecked" })
   //@Transactional(readOnly = false, propagation=Propagation.REQUIRED)
   public T find(Long id) {
     log.debug("find!!" + id); 
         
     return (T) em.find(reflectionUtil.getModelClass(this), id);
   }

   /**
    * Find all entities.
    */
   @SuppressWarnings("unchecked")
   //@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
   public List<T> findAll() {
     log.debug("JpaServiceImpl-findAll");
     String queryStr = new StringBuilder().append("select t FROM ").append(
      reflectionUtil.getModelClass(this).getName()).append(" t").toString(); //getSimpleName()
     log.debug(queryStr);
     Query query = em.createQuery(queryStr);
     
     return query.getResultList();
   }
   
   public long count() {
     String queryStr = new StringBuilder().append("SELECT COUNT(t) FROM ").append(reflectionUtil.getModelClass(this).getName()).append(" t").toString(); 
     Query query = em.createQuery(queryStr);
     return (Long) query.getSingleResult();
   }

}

Note that even JpaServiceImpl doesnt have any abstract method it is still set to be abstract class. I did this just to enforce that for each entity there is its own implementation of service, since we always going to need few extra special methods for them.

ReflactionUtil its a small class to resolve model beeing used in service and to get id value for it, and looks like this
package test.service.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.Id;

import org.apache.log4j.Logger;


public class ReflectionUtil {
 /** Logger */
 private static final Logger log = Logger.getLogger(ReflectionUtil.class);

 /** Cache Map of scanned obj */
 private Map map = new HashMap();
 
 /** single instance */
 private static ReflectionUtil instance;
 private ReflectionUtil() {}
 
 public static ReflectionUtil get() {
  if (instance == null) {
   instance = new ReflectionUtil();
  }
  return instance;
 }
 
 public Class getModelClass(Class cls) {
  String key = "modelClass-" + cls.getName();
  if (!map.containsKey(key)) { 
   Class<?> modelClass = (Class<?>) (((ParameterizedType) (cls
    .getGenericSuperclass())).getActualTypeArguments()[0]);
   map.put(key, modelClass);
  }
  return (Class<?>) map.get(key);
 }
 
 public Class<?> getModelClass(Object obj) {
  return getModelClass(obj.getClass());
 }
 
 public Object getIdValue(Object t) {
  try {
   Method method = getIdMethod(t);
   return (Object) method.invoke(t, null);
  } catch (Exception e) {
   log.error(e);
   return null;
  }
 }
 
 private Method getIdMethod(Object t) throws SecurityException, NoSuchMethodException {
  String key = t.getClass().getName();
  //key + "-method"
  if (map.get(key + "-method") == null) {
   String methodNameForId = getMethodNameForId(t);
   log.debug("methodName: " + methodNameForId);
   Method m = t.getClass().getMethod(methodNameForId, null);
   map.put(key + "-method", m);
  }
  return (Method) map.get(key + "-method");
 }
 
 public String getPropertyNameForId(Object t){
  return getMethodOrPropertyNameForId(t, "-propertyNameForId");
 }
 public String getMethodNameForId(Object t){
  return getMethodOrPropertyNameForId(t, "-methodNameForId");
 }
 
 private String getMethodOrPropertyNameForId(Object t, String subKey){
  String key = t.getClass().getName();
  
  if (map.get(key) == null || map.get(key + subKey) == null ) {
   Class<?> modelClass = getModelClass(t);
   // set methodNameForId
   // finds getter name for field which has id annotation
   for (Field f : modelClass.getDeclaredFields()) {
    if (f.isAnnotationPresent(Id.class)) {
     String propertyNameForId = f.getName();
     map.put(key + "-propertyNameForId", propertyNameForId);
     String methodNameForId = "get" + capitalize(f.getName());
     map.put(key + "-methodNameForId", methodNameForId);
     break;
    }
   }
  }
  
  return (String) map.get(key + subKey);
 }
 
 /** Capitalize string */
 private String capitalize(String str) {
  return str.substring(0, 1).toUpperCase()
    + str.substring(1, str.length());
 }
}

After that you extend interface like this
package test.service;

import java.util.List;

import test.model.Person;

public interface PersonService extends JpaService<Person> {
 List<Person> findByLastName(String name);
}

And we can extend implementation service like this
package test.service.impl;

import java.util.List;

import javax.persistence.Query;

import test.model.Person;
import test.service.PersonService;


public class PersonServiceImpl extends JpaServiceImpl<Person> 
  implements PersonService {

 public List<Person> findByLastName(String name) {
  Query query = em.createQuery("SELECT p FROM " + reflectionUtil.getModelClass(this).getName() +
          " p WHERE p.lastName = '" + name + "' ");
     return query.getResultList();
 }

}

Tell me do you use something similar or?

This week win: Install Oracle 11g database on vmware Ubuntu 11

I don't love admin task, as any other developer i presume, but any now and then you get one. This week it was Installing Oracle 11.2 g database on vmware Ubuntu 11.04 Natty Narwhal.

Problem is that Oracle doesn't support officially Ubuntu, so you are pretty much on your own. Actually i had only two problems:


Oracle install wizard in one of steps checks your system settings do they satisfy requrments. For some OS settings it can generate repair script for you to run runfixup.sh.

That scripts calls orarun.sh, you just need to change second one from sh to bash script, change first line from

#!/bin/sh

to

#!/bin/bash


Second problem was Oracle needs some libs to perform linking so you need to add this misssing libs for oracle linking

sudo apt-get install build-essential
sudo apt-get install libaio1
sudo apt-get install libaio-dev
sudo apt-get install unixODBC
sudo apt-get install unixODBC-dev
sudo apt-get install pdksh
sudo apt-get install expat
sudo apt-get install sysstat
sudo apt-get install libelf-dev
sudo apt-get install elfutils
sudo apt-get install lsb-cxx

http://forums.oracle.com/forums/thread.jspa?threadID=1115155

If some of them your ubuntu can not find you will need to find them on net and add them in your source list on end of file.

sudo vi /etc/apt/sources.list

add line like this:
deb http://archive.canonical.com/ubuntu/ natty partner

Use JPA with MongoDb and Datanucleus



Web applications nowadays have huge demand for data processing, and they need to get them, process them, and displayed them fast. Traditional relational tables and big SQL engines fail to serve that purpose.

NoSQL movement is on the rise. There are huge numbers of alternatives to SQL like BigTable, HBase, CouchDB, Cassandra and MongoDB. They are all fast but you'll need to implement new way to work with them, since all of them have they own query languages. It would be nice that we can use them in similar or same way in our projects, after all ORM layer is there for that to decouple our objects from underlying storage.

MongoDB is one of most popular NoSQL solutions and easiest one to install. For HBase i would had to install cygwin or virtual linux, and still will be hard to confgiure it right. MongoDb is easy, it plays out of the box, just unzip it, and it still offers some nice features like MapReduce, Replication and Sharding.

Datanucleus is one of the leading ORM providers (it is old JPOX project) and they have added support and for JPA. I run in to them after they change name from JPOX when Google chose them to use it for its app engine cloud solution.

In the newest version 3.0m5 of datanucleus, they added support for JPA2 standard and with usual support for all classic RMDBS (Oracle, MySql...) they also added and NoSQL solutions like BigTable, HBase and MongoDB.

So first you will need to install MongoDB from download page, and follow quick start.

But in short you download zip, unzip it.
Create /data/db folder in your root, and you are erady.

Start MongoDB server by going to bin folder and type mongod.

Next you should start mongo shell and test is your MongoDB is working. Go to bin and start mongo.


To list dbs, use
> show dbs

To change db
> use db_name

To list collections/tables in db
> show collections

To add values in
> db.collection_name.insert( { a : 1 } )

To list all values in collection
> db.collection_name.find()

To remove all values in collection
> db.collection_name.remove()


To create small JPA and MongoDB project you can download datanucleus access platform for mongodb from here.

If you use eclipse you can find there and datanucleus plugin that will help you enchance your entities.

Create first small entity called person.

import javax.persistence.*;

@Entity
@Table(name="persons")
public class Person {
  @Id 
  @GeneratedValue(strategy=GenerationType.AUTO)
  @Column(name="_id") // comment this if you use alternative
  private String id;  // comment this if you use alternative
  // alternative to string mongo id, but then remove name '_id'
  // private Long id;
 
  private String firstName = null;
  private String lastName = null;
  private int level = 0;
 
  // getters and setters
  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 int getLevel() {
    return level;
  }
  public void setLevel(int level) {
    this.level = level;
  }
 
  // to string
  @Override
  public String toString() {
    return "Person [id=" + id + ", firstName=" + firstName + ", lastName="
    + lastName + ", level=" + level + "]";
  }
 
 
}

Note, you can use and long as id but then some type of searches will be slower.
If your project needs to work and with regular SQL DBs you should use long id, otherwise if project will stay forever with MongoDB use string for id since that is natural mongo id.

Then you need to add persistance.xml to your META-INF folder and specify there connection to your mongoDB.

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <!-- Tutorial "unit" -->
    <persistence-unit name="Tutorial">
        <properties>
            <property name="datanucleus.ConnectionURL" value="mongodb:localhost/mng1"/>
            <property name="datanucleus.storeManagerType" value="mongodb" />
            <property name="datanucleus.autoCreateSchema" value="true" />
        </properties>
    </persistence-unit>

</persistence>

Libs that you will need for this projects are:
datanucleus-core-3.0.0-m5.jar
datanucleus-mongodb-3.0.0-m5.jar
datanucleus-api-jpa-3.0.0-m5.jar
geronimo-jpa_2.0_spec-1.0.jar
jdo-api-3.1-SNAPSHOT-20110319.jar
transaction-api-1.1.jar
mongo-java-driver-2.6.3.jar
log4j-1.2.16.jar

And add simple Main class that will create entity manager and do few CRUD operations.

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

import test.model.Person;



public class Main {

    public static void main(String[] args) {
        System.out.println("DataNucleus Tutorial with JPA");
        System.out.println("=============================");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Tutorial");
        
        // Create entity manager
        EntityManager em = emf.createEntityManager();
        // if you need transactions i will not use them
        //EntityTransaction tx = em.getTransaction();
        
        System.out.println("Save entities");
        System.out.println("=============================");
        Person p = new Person();
        p.setFirstName("test-name-1");
        p.setLastName("test-lastn-1");
        p.setLevel(5);
        
        em.persist(p);
        String id = p.getId();
        System.out.println("id: " + id);
        
        p = new Person();
        p.setFirstName("test-name-2");
        p.setLastName("test-lastn-2");
        p.setLevel(4);
        
        em.persist(p);
        id = p.getId();
        System.out.println("id: " + id);
        // to save for sure in db
        em.close();
        
        System.out.println("find by id");
        System.out.println("=============================");
        em = emf.createEntityManager();
        Person p2 = em.find(Person.class, id);
        System.out.println("found: " + p2.toString());
        
        
        //em = emf.createEntityManager();
        System.out.println("query");
        System.out.println("=============================");
        String qstr = "SELECT p FROM " + Person.class.getName() +
            " p WHERE p.level >= 3 ";
        System.out.println("query: " + qstr);
        Query q = em.createQuery(qstr);
        List list = (List) q.getResultList();
        for (Person per : list) {
            System.out.println("person > " + per.toString());
        }
        
        System.out.println("Remove from db");
        System.out.println("=============================");
        for (Person per : list) {
            em.remove(per);
        }
        em.close();
    }

}

You just need to compile and run your project. For compile you can use maven. pom.xml that looks like this. If you use ant or you want to do it without compile scripts note that datanucleus needs to enchance (some type of compile time weaving) entity classes before you run your project.

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test.jpa.mongodb</groupId>
    <artifactId>datanucleus-example-jpa-mongodb</artifactId>
    <packaging>jar</packaging>
    <version>4.0</version>
    <name>DataNucleus AccessPlatform Sample for MongoDB with JPA</name>
    
    <repositories>
        <repository>
            <id>DN_M2_Repo</id>
            <name>DataNucleus Repository</name>
            <url>http://www.datanucleus.org/downloads/maven2</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>DataNucleus_2</id>
            <url>http://www.datanucleus.org/downloads/maven2/</url>
        </pluginRepository>
    </pluginRepositories>
    
    <dependencies>
        <dependency>
            <groupId>org.datanucleus</groupId>
            <artifactId>datanucleus-core</artifactId>
            <version>[2.9, )</version>
        </dependency>
        <dependency>
            <groupId>org.datanucleus</groupId>
            <artifactId>datanucleus-mongodb</artifactId>
            <version>[2.9, )</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>[1.2, 1.3)</version>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>org.datanucleus</groupId>
            <artifactId>datanucleus-api-jpa</artifactId>
            <version>[2.9, )</version>
        </dependency>
        <dependency>
            <groupId>javax.jdo</groupId>
            <artifactId>jdo-api</artifactId>
            <version>[3.0, )</version>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.specs</groupId>
            <artifactId>geronimo-jpa_2.0_spec</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    
    <build>
        <sourceDirectory>src/main/java</sourceDirectory>
        <outputDirectory>bin</outputDirectory>

        <resources>
            <resource>
                <directory>${basedir}/src/main/java</directory>
                <includes>
                    <include>**/persistence.xml</include>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.datanucleus</groupId>
                <artifactId>maven-datanucleus-plugin</artifactId>
                <version>3.0.0-m1</version>
                <configuration>
                    <log4jConfiguration>${basedir}/log4j.properties</log4jConfiguration>
                    <verbose>false</verbose>
                    <api>JPA</api>
                    <persistenceUnitName>Tutorial</persistenceUnitName>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>enhance</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.1</version>
                <configuration>
                    <mainClass>test.service.Main</mainClass>
                    <systemProperties>
                        <systemProperty>
                            <key>log4j.configuration</key>
                            <value>file:${basedir}/log4j.properties</value>
                        </systemProperty>
                    </systemProperties>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptors>
                        <descriptor>${basedir}/assembly.xml</descriptor>
                    </descriptors>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

To compile project, just start
> mvn clean compile

And to run main
> mvn exec:java

The zip with project files can be found here, source download or here .

Tell me what do you think about article, do you need NoSQL in your project, is JPA with NoSQL good or bad thing for you?