ICE09 . playing with java, scala, groovy and spring .

Playing with Spring

Google App Engine, GWT, Spring 3 and JPA

Posted by ice09 on May 25, 2009

If you are interested in Spring 3 on the GAE, there is a newer post about just this topic (and some REST!).
UPDATE: The github repository for this post is here. A prepared Eclipse project can be downloaded and imported into Eclipse with the installed Eclipse Google plugin (instructions below, to download a zipped project, press on the github site).

Being really enthusiastic about Java on the Google App Engine, I tried out some simple examples, which I will document here.
Most interesting for me was the combination of the Google App Engine, GWT and – of course – the Spring Framework.
Since the current version of Spring is version 3.0, I tried this version, together with the new Google Eclipse plugin, which currently brings the App Engine SDK 1.2.1 and GWT 1.6.4.
The Eclipse version is Ganymede 3.4.2.

References

Most of this stuff is already described, I will list all useful sites here with a short summary – this post is just a mash-up of the mini-tutorials on these sites.

The first entry point for developing for the Google App Engine is obviously the documentation of Google itself. It is well written, easy comprehensible and there is a lot to find for this early stage.

There are different other solutions described here, here and here. However, these solutions did not fit the demands of low impact, current version compatibility or just simple usage.

Preparation

  • First, a “Google” Web Application is created by File > New > Web Application Project

Project setup

First, the added libraries.

  • Libraries added to WEB-INF/lib and the to the project’s classpath
  • Servlet jar is included in lib directory but not added to classpath

Besides the jar libraries, several configuration files and Java classes had to be included

  • Java classes to be included or changed (detailed description below)
  • Configuration files to be included or changed (detailed description below)

Function

The most innovative part is taken from here and described in detail.

The following parts have been added:

Below are the missing parts.

EMF.java

package com.commons.server;

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

import org.springframework.stereotype.Component;

@Component
public class EMF {

	private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    public EMF() {}

    public EntityManager entityManager() {
        return emfInstance.createEntityManager();
    }
}

persistence.xml

<?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">
<persistence-unit name="transactions-optional">
<provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
        <class>com.commons.server.Customer</class>
<properties>
<property name="datanucleus.NontransactionalRead" value="true"/>
<property name="datanucleus.NontransactionalWrite" value="true"/>
<property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>

</persistence>

Customer.java

package com.commons.server;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    public Long getId() {
        return id;
    }
    public void setId(Long 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;
    } 

}

GreetingsServiceImpl.java

package com.commons.sigae.server;

import java.util.Collection;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Component;

import com.commons.sigae.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("serial")
@Component
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {

 	private boolean first;
	protected EntityManager entityManager;
	
	@Autowired
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	public JpaTemplate getTemplate() {
		return new JpaTemplate(entityManager);
	}
	
	public String greetServer(String input) {
		return getData(input);
	}

	private String getData(String input) {
		String all = "";
		for (Customer customer : getCustomers()) {
			if (!first) {
				removeCustomer(customer);
			}
		}
		first = true;
		for (Customer customer : getCustomers()) {
			all += "id: " + customer.getId() + " - firstname: " + customer.getFirstName() + " - name:" + customer.getLastName();
		}
		createCustomer(input);
		return all;
	}

	private void removeCustomer(Customer customer) {
		EntityTransaction trx = entityManager.getTransaction();
		trx.begin();
		entityManager.remove(customer);
		trx.commit();
	}

	private void createCustomer(String input) {
		EntityTransaction trx = entityManager.getTransaction();
		Customer newCustomer = new Customer();
		newCustomer.setFirstName(input + System.currentTimeMillis());
		newCustomer.setLastName(input + System.currentTimeMillis());
		trx.begin();
		entityManager.persist(newCustomer);
		trx.commit();
	}

	private Collection<Customer> getCustomers() {
		return getTemplate().execute(new JpaCallback<Collection<Customer>>() {
			@Override
			public Collection<Customer> doInJpa(EntityManager arg0) throws PersistenceException {
				return (Collection<Customer>) entityManager.createQuery("SELECT cust FROM com.commons.sigae.server.Customer cust").getResultList();
			}
		});
	}

}

dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="com.commons" />

	<bean id="entityManager" factory-bean="EMF" factory-method="entityManager" />

	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<value>
				/springingae/greet=gwtController
			</value>
		</property>
	</bean>
	
  	<bean name="gwtController" class="com.commons.sigae.server.GWTController">
    	        <property name="remoteService" ref="greetingServiceImpl"/>
  	</bean>	

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

	<!-- Default page to serve -->
	<welcome-file-list>
		<welcome-file>SpringInGAE.html</welcome-file>
	</welcome-file-list>

	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/springingae/greet</url-pattern>
	</servlet-mapping>


</web-app>

Tipps & Tricks:

Advertisements

15 Responses to “Google App Engine, GWT, Spring 3 and JPA”

  1. Edwin said

    Very usefull, thank you… but I have a question: Can I send jpa objects to the client?

    • ice09 said

      Hi Edwin,

      phew, I played around and came across several problems, all related to Serialisation issues:

      1. Serializable or IsSerializable must be implemented and
      2. an empty constructor must be provided
      3. Customer had to be moved to the client package

      …but there I am stuck, this seems to be a common problem: the JPA class is enriched with jdoDetachedState, which is an Object array and this cannot be serialized.

      Compare this post:

      http://code.google.com/p/google-web-toolkit/issues/detail?id=3279#c4

      which imho also provides the best (and most painful) solution (Comment 3): creating data transfer objects for the JPA ones – this fits best the bridge between Java and JavaScript.

      However, there might be easier solutions (I am definitely not an expert in GWT) which I would be glad to see (eg. influencing the creation of the jdoDetachedState or changing the type to Serializable[] etc.)

      Best,

      ice09

  2. asianCoolz said

    return (Collection) entityManager.createQuery(“SELECT cust FROM com.commons.sigae.server.Customer cust where firstname = ‘test'”).getResultList();

    are you able to execute statementl like above in gae? i get zero result

  3. asianCoolz said

    agreed. can you please help try SELECT cust FROM com.commons.sigae.server.Customer cust where cust.firstname = ‘test’ on your testapp and post your result? I have strange output of zero and checking any other developer having same problem

    • ice09 said

      Hi asianCoolz,

      yes, it works for me, I just removed the timestamp from the createCustomer Method and changed the Statement to

      SELECT cust FROM com.commons.sigae.server.Customer cust where cust.firstName=’test’

      Please note the cust.firstName (so prefix ‘cust’ and firstName camelcased). This works fine for me.

  4. asianCoolz said

    ya it work.

    how about

    SELECT cust FROM com.commons.sigae.server.Customer cust where cust.firstName like ’t%’

    let say you have 18 records with username like ‘test’,’test’…until ‘test’ (18 times) . does it list all ? i able to get the result, but it is less than correct result suppose to be 18 instead, i get 7.

    can try this?

  5. Gnuce Lee said

    Is it possible to connect to some other database this way? I would like to know, if I can use MySQL or Oracle from local App Engine?

    • ice09 said

      Hi Gnuce,

      no, this is not possible, Google App Engine does not support relational databases.
      However, you can use abstractions like JPA or JDO.

    • Juri said

      @Gnuce: it wouldn’t make a lot of sense to use Oracle or MySQL from a local App Engine if App Engine doesn’t support it. Using App Engine makes just sense if you actually want to upload your server and host it there.

  6. I’m trying to use the EMF component like you are doing, and it gets injected perfectly, though when I try to use it, it appears to be null.

    Asked it on stackoverflow too:
    http://stackoverflow.com/questions/2593678/why-is-the-entitymanager-in-my-gae-spring-graniteds-project-reset-to-null

    Perhaps you have an idea?

    Thanks and kind regards,

    Jochen

  7. jimmy6 said

    Do you know how to use use transaction management from spring by aop to avoid those trx.commit() code?

    • ice09 said

      Hi Jimmy6,

      actually, I did not know, but looked it up and as you mentioned, it does definitely make sense in this example.
      However, I am planning do to a complete rewrite of some posts (this is one of them) and this will be fixed only by then.

      Best, ice09

  8. Chandan said

    Hello,
    I have 2 queries regarding the above example:

    1) Will it create the database table for us, or we need to create the Customer table manually?

    2) What is the role of GWTController? How it will come into play for above example?

    regards,
    Chandan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: