Hibernate, Spring, Google Web Toolkit – Part Three
The first thing we need to do in part three is begin GWT’s Remote Procedural Call (RPC) plumbing. We use the RPC calls to fire off Asynchronous events so that we can get data from our database. The calls go to the server effectively. Firstly I have made a package called com.company.client.rpc and this is where we’ll make our RPC interfaces.
Firstly make a interface called TeacherService as listed below.
package com.company.client.rpc; import java.util.List; import com.google.gwt.user.client.rpc.RemoteService; public interface TeacherService extends RemoteService { /** * @gwt.typeArgs <com.company.client.dto.PupilDTO> */ public List getPupils(Integer teacherId); }
A few things to notice here are that the interface extends RemoteService which is a GWT class and is part of the RPC plumbing. Also you’ll note that the return type is not typed as in List
Now we need to make our Async version of the interface. Make a new interface within the same packaged named TeacherServiceAsync. The Async appendage is important because GWT uses this convention to identify async classes. The code is listed below:
package com.company.client.rpc; import com.google.gwt.user.client.rpc.AsyncCallback; public interface TeacherServiceAsync { public void getPupils(Integer teacherId, AsyncCallback callback); }
Notice that we have changed the return type to void and added a parameter to the method signature that is of type AsyncCallback this is also very important and you’ll see why later. The callback parameter must always be last so if your methods take a few arguments make sure your AsyncCallback argument is last. Now to write the classes on the server side of code. Here we will drag some useful functionality from Spring and implement Transactional Database calls.
Firstly make a package called com.company.server.gwt. Then we can create our RemoteServiceServlet. The class beginnings is listed below.
package com.company.server.gwt; import java.util.List; import com.company.client.dto.PupilDTO; import com.company.client.rpc.TeacherService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; public class TeacherServiceImpl extends RemoteServiceServlet implements TeacherService { /** * */ private static final long serialVersionUID = 1L; public List<PupilDTO> getPupils(Integer teacherId) { // TODO Auto-generated method stub return null; } }
Notice we can fully type our list as we are coding server side code and that the interface extends RemoteServiceServlet which is a required part of the the RPC plumbing. For now leave that class there and we will make the database code.
Make a new package called com.company.server.domain (obviously package names are up to you my convention is hazy sometimes…) I tend to work with collections of data and have a convention that server classes extend java.util.List and then we can ask for any element from the list. Firstly make an interface called Pupil Collection that extends List. You can also use java generics as I have done if required. Code below:
package com.company.server.domain; import java.util.List; public interface PupilCollection<T> extends List<T> { /** * * @return The list associated with the class */ public List<T> getList(); /** * Populate the list for the class with some pupils * * @param teacherId */ public void getPupils(Integer teacherId); }
Now we’ll write the implementing class and this will contain the true database calls to fetch data also any delegate methods for our List object. Code listed below.
package com.company.server.domain; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.transaction.annotation.Transactional; public class PupilCollectionImpl<T> extends HibernateDaoSupport implements PupilCollection<T> { private List<T> list = null; public List<T> getList() { return list; } @Transactional(readOnly=true) public void getPupils(Integer teacherId) { try { list = getHibernateTemplate().findByNamedQueryAndNamedParam("pupilsbyid", "teacherId", teacherId); } catch(Exception e ) { e.printStackTrace(); } } /// DELEGATE METHODS REQUIRED FOR OUR LIST //// /// NOT IMPLEMENTED AS NOT USED BY EXAMPLE //// public boolean add(T o) { // TODO Auto-generated method stub return false; } public void add(int index, T element) { // TODO Auto-generated method stub } ...
This is a very important class things to note are the use of the @Transactional annotation above the method. This alerts Spring that the method should be executed in one transaction. The second thing is Hibernate query call findByNamedQueryAndNamedParam(“pupilsbyid”, “teacherId”, teacherId) pupilsbyid is our query name, teacherId as String is the parameter name and teacherID as Integer is the actual ID we are looking for.
. Now to go ahead and write our query into our mapping file.
In the hibernate.cfg.xml file add the line:
<mapping resource="database.hbm.xml"/>This allows us to use a hibernate mapping file to put our queries into. Now in the src folder create a file called database.hbm.xml code listed below
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <query name="pupilsbyid"><![CDATA[ from com.company.server.persistent.Pupil as p where p.teacher.teacherId = :teacherId order by p.name ]]> </query> </hibernate-mapping>
You can now see our query name and the parameter specification as mentioned before. This will get us all the pupils for a certain teacher.
Thats our server database code complete. Now we can go back to the unfinished RemoteServiceServlet class and use a bit of Spring magic to make the call. Firstly we need to define a bean for our PupilCollectionImpl so find the applicationContext.xml file that was created in the earlier tutorials and add the line:
<bean id="pupilCollection" class="com.company.server.domain.PupilCollectionImpl" scope="prototype"> <property name="sessionFactory"><ref bean="sessionFactory"/></property> </bean>
Now thats done we need to write a way to get the beans, make a new package called com.company.spring and add the following class
package com.company.spring; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ContextUtil { private static ApplicationContext context = null; private ContextUtil( ) { super(); } public static ApplicationContext getContext( ) { // Load up our Application context if (context == null) { context = new ClassPathXmlApplicationContext("applicationContext.xml") ; } return context; } }
This class is used to load our applicationContext and get any required beans. You can of course do this differently.
This allows us get access to the database methods we just wrote and also the database details through the use of referencing the session factory bean within the definition. Now to call trhe bean revisit the TeacherServiceImpl class and add the following lines:
public ListgetPupils(Integer teacherId) { List results = null; try { PupilCollection> collection = (PupilCollection>) ContextUtil.getContext().getBean("pupilCollection"); collection.getPupils(teacherId); results = new ArrayList (); for ( Iterator> it = collection.getList().iterator(); it.hasNext(); ) { Pupil pupil = (Pupil) it.next(); PupilDTO dto = new PupilDTO(); BeanUtils.copyProperties(dto, pupil); results.add(dto); } } catch(Exception e) { e.printStackTrace(); throw new RuntimeException(e); } return results; }
Notice the name of the bean we are getting pupilCollection’ which we defined in our applicationContext. Is it coming together now?
Thats basically our server side code now complete. We now have some methods that we can call upon to load data. Woohoo!
The next thing to do is perform the calls from the client end somehow. For this I use an MVC approach and implement the observer
pattern as specified by the Gang of 4 Design Patterns.
Firstly make a package named com.company.client.model and in there add two interfaces Observer and Subject. Code listed below:
package com.company.client.model; public interface Subject { public void addObserver( Observer o ); public void removeObserver( Observer o ); }
package com.company.client.model; public interface Observer { public void update( Subject o ); public String toString(); }
Next make our model that implements Subject. This is used so that we can make the RPC calls from the client end and successfully update GUI
views. I have made a TeacherModel class that implements Subject. Code is listed below.
package com.company.client.model; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.company.client.rpc.TeacherService; import com.company.client.rpc.TeacherServiceAsync; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; public class TeacherModel implements Subject { private List pupils = null; protected ArrayList observers = null; ///// Observer Methods //////// public void addObserver(Observer o) { if ( observers == null ) { observers = new ArrayList(); } observers.add(o); } public void removeObserver(Observer o) { if ( observers != null ) { observers.remove(o); } } public void notifyObservers() { Iterator it = observers.iterator(); while( it.hasNext() ) { Observer o = ( Observer ) it.next(); o.update( this ); } } public void getPupils(Integer teacherId) { TeacherServiceAsync teacherService = (TeacherServiceAsync) GWT.create(TeacherService.class); ServiceDefTarget endpoint = (ServiceDefTarget) teacherService; String moduleRelativeURL = GWT.getModuleBaseURL() + "teachers"; endpoint.setServiceEntryPoint(moduleRelativeURL); teacherService.getPupils(teacherId, new AsyncCallback() { public void onFailure(Throwable caught) { // TODO Auto-generated method stub } public void onSuccess(Object result) { pupils = (List) result; notifyObservers(); } }); } public List getPupils() { return pupils; } public void setPupils(List pupils) { this.pupils = pupils; } }
I won’t go into details about the implementation of the design pattern for that read the book its worth it! The getPupils method is the important one this is where we perform the RPC call, notice we look for a URL of “teachers” this is defined in the gwt.xml file so lets define it now. In the MyApplication.gwt.xml file add the line:
<servlet path="/teachers" class="com.company.server.gwt.TeacherServiceImpl"/>This makes that RPC now work. Now inspect the anonymous inner callback class in my implementation this is used to update and notify any observers on a successful call. Also note you can cast the result to the type it is meant to be. For example a save method could return a boolean.
At this stage you can test the call to ensure that it is working by simply making a new TeacherModel and calling getPupils. The final step is to write the
client GUI code. I have wrote a simply GUI that takes an ID and performs a search for pupils when the users click search, when the search has finished the view is updated in a GWT way. The code is below and may help bring things together for the whole Async way of doing things.
package com.company.client; import java.util.Iterator; import com.company.client.dto.PupilDTO; import com.company.client.model.Observer; import com.company.client.model.Subject; import com.company.client.model.TeacherModel; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; public class MyApplication implements EntryPoint, Observer { private TeacherModel model = null; private VerticalPanel display = null; private VerticalPanel resultsPanel = null; /** * This is the entry point method. */ public void onModuleLoad() { model = new TeacherModel(); display = new VerticalPanel(); model.addObserver(this); HorizontalPanel searchPanel = new HorizontalPanel(); final TextBox teacherIdBox= new TextBox(); final Button button = new Button("Search"); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { resultsPanel.add(new HTML("Searching....")); model.getPupils(new Integer(teacherIdBox.getText())); } }); searchPanel.add(teacherIdBox); searchPanel.add(button); resultsPanel = new VerticalPanel(); display.add(searchPanel); display.add(resultsPanel); RootPanel.get("database").add(display); } public void update(Subject o) { if ( model.getPupils() != null && model.getPupils().size() > 0) { resultsPanel.clear(); for( Iterator it = model.getPupils().iterator(); it.hasNext();) { PupilDTO dto = (PupilDTO)it.next(); resultsPanel.add(new HTML(dto.getName())); } } } }
I hope this tutorial has helped clear up some issues. The final tutorial will explain how make the application work as a live application and not just in GWT hosted mode. The Eclipse project can be downloaded here and can be used as a template for projects. I would still urge people to read the tutorial.