William has posted 6 posts at DZone. View Full User Profile

Enterprise RIA with Spring 3, Flex 4 and GraniteDS

01.25.2011
| 47206 views |
  • submit to reddit

Persistence and integration with Hibernate/JPA

Let's go a bit further and see how to do simple CRUD with a couple of JPA entities :

@Entity
public class Author extends AbstractEntity {

@Basic
private String name;

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="author", orphanRemoval=true)
private Set books = new HashSet();

// Getters/setters
...
}

@Entity
public class Book extends AbstractEntity {

@Basic
private String title;

@ManyToOne(optional=false)
private Author author;

// Getters/setters
...
}
Both entities extends AbstractEntity, but it is not mandatory at all, it's just a helper class provided by the Maven archetype.

 

Now we create a simple Spring service to handle basic CRUD for these entities (obviously a politically correct Spring service should use DAOs, but DAOs are awful and won't change anything here) :

@RemoteDestination
public interface AuthorService {

public List<author> findAllAuthors();

public Author createAuthor(Author author);

public Author updateAuthor(Author author);

public void deleteAuthor(Long id);
}

@Service
public class AuthorServiceImpl implements AuthorService {

@PersistenceContext
private EntityManager entityManager;

@Transactional(readOnly=true)
public List<author> findAllAuthors() {
return entityManager.createQuery("select a from Author a order by a.name").getResultList();
}

@Transactional
public Author createAuthor(Author author) {
entityManager.persist(author);
entityManager.refresh(author);
return author;
}

@Transactional
public Author updateAuthor(Author author) {
return entityManager.merge(author);
}

@Transactional
public void deleteAuthor(Long id) {
Author author = entityManager.find(Author.class, id);
entityManager.remove(author);
}
}</author></author>
And the Flex application :
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns="*"
preinitialize="init()">

<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.data.utils.Managed;
import org.granite.tide.spring.Spring;
import org.granite.tide.service.DefaultServiceInitializer;
import org.granite.tide.events.TideResultEvent;
import org.granite.tide.events.TideFaultEvent;
import org.granite.tide.TideResponder;

import org.example.entities.Author;
import org.example.services.AuthorService;

private function init():void {
Spring.getInstance().initApplication();
Spring.getInstance().getSpringContext().serviceInitializer = new DefaultServiceInitializer('/gdsspringflex');
}

[Inject]
public var authorService:AuthorService;


[Bindable]
public var authors:ArrayCollection;

private function findAllAuthors():void {
authorService.findAllAuthors(
function(event:TideResultEvent):void {
authors = ArrayCollection(event.result);
}
);
}

[Bindable]
private var author:Author = new Author();

private function createAuthor():void {
authorService.createAuthor(author,
function(event:TideResultEvent):void {
authors.addItem(author);
author = new Author();
},
function(event:TideFaultEvent):void {
Alert.show(event.fault.toString());
}
);
}

private function editAuthor():void {
currentState = 'edit';
author = Author(lAuthors.selectedItem);
}

private function updateAuthor():void {
authorService.updateAuthor(lAuthors.selectedItem,
function(event:TideResultEvent):void {
lAuthors.selectedItem = null;
author = new Author();
currentState = 'create';
},
function(event:TideFaultEvent):void {
Alert.show(event.fault.toString());
}
);
}

private function cancelAuthor():void {
lAuthors.selectedItem = null;
author = new Author();
currentState = 'create';
}

private function deleteAuthor():void {
authorService.deleteAuthor(lAuthors.selectedItem.id,
function(event:TideResultEvent):void {
var idx:int = authors.getItemIndex(lAuthors.selectedItem);
authors.removeItemAt(idx);
lAuthors.selectedItem = null;
author = new Author();
currentState = 'create';
},
function(event:TideFaultEvent):void {
Alert.show(event.fault.toString());
}
);
}
]]>
</fx:Script>

<s:states>
<s:State name="create"/>
<s:State name="edit"/>
</s:states>

<s:Group width="800">
<s:layout>
<s:VerticalLayout paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10"/>
</s:layout>

<mx:Form id="fAuthor">
<mx:FormHeading label.create="New author" label.edit="Edit author"/>

<mx:FormItem label="Name">
<s:TextInput id="iName" text="@{author.name}"/>
</mx:FormItem>
<mx:FormItem>
<s:HGroup>
<s:Button id="bSave" label.create="Create" label.edit="Update"
click.create="createAuthor()"
click.edit="updateAuthor()"/>
<s:Button id="bDelete" label="Delete" visible.create="false" visible.edit="true"
click.edit="deleteAuthor()"/>
<s:Button id="bCancel" label="Cancel" visible.create="false" visible.edit="true"
click.edit="cancelAuthor()"/>
</s:HGroup>
</mx:FormItem>
</mx:Form>

<s:Label fontWeight="bold" text="Authors List"/>
<s:List id="lAuthors" dataProvider="{authors}" labelField="name" width="100%"
change="editAuthor()"
creationComplete="findAllAuthors()"/>

<s:Button label="Refresh" click="findAllAuthors()"/>

</s:Group>

</s:Application>

This is a simple CRUD application using some very convenient Flex 4 features such as states and bidirectional data binding. Note how all boilerplate code to connect the client and the server has litterally disappeared while still keeping a clean separation between the two layers. In the real world we would probably want to apply some MVC pattern instead of a monolithic mxml but this would not change much. The important thing is that the Flex and Java parts do not contain useless or redundant code and are thus a lot easier to maintain. Even using model-driven code generators to build the Flex application automatically would be easier because there is basically much less code to generate.

Now rebuild and run the application on Jetty, and check that you can create, update and delete authors. There are two minor issues with this example that have not much interest in themselves but that I will use to show two interesting features of Tide.

First issue : when you start updating the name of an author, the change is propagated to the list by bidirectional binding but the previous value is not restored when you click on 'Cancel', leading to an inconsistent display. This is mainly a problem of Flex 4 bidirectional binding because it propagates all changes immediately but it is not able to rollback these changes. We would have three options to fix it : save the entity state somewhere before editing and restore it upon cancel, copy the original data and bind the input fields on the copy (but then the list would not be updated by binding), or avoid using bidirectional binding. None of these options is really appealing, fortunately Tide provides a very simple feature to deal with this and makes bidirectional binding really usable :

private function cancelAuthor():void {
Managed.resetEntity(author);
lAuthors.selectedItem = null;
author = new Author();
currentState = 'create';
}
Managed.resetEntity() simply rolls back all changes done on the Flex side and restores the last stable state received from the server.

 

Second issue : clicking on refresh loses the current selected item. This is because we replace the dataProvider of the list each time we receive a new collection. This can easily be fixed by using the data merge functionality of Tide :

public var authors:ArrayCollection = new ArrayCollection();

private function findAllAuthors():void {
authorService.findAllAuthors(new TideResponder(null, null, null, authors));
}
This relatively ugly remote call using a TideResponder indicates to Tide that it should merge the call result with the provided variable instead of completely replacing the collection with 'authors = event.result' in the result handler. Note that we don't even need a result handler any more, once again saving a few lines of codes.

 

This issue with item selection illustrates why keeping the same collection and entity instances accross remote calls is very important if you want to setup data-driven visual effects or animations (remember, the R of RIA). All visual features of Flex highly depend on the object instance that drives the effect and the events that it dispatches. This is where the Tide entity cache and merge helps a lot by ensuring that each entity instance will exist only once and dispatch only the necessary events when it is updated from the server.

I'll finish this part by showing how to display and update the collection of books. For now you have maybe noticed that the existence of this collection did not cause any problem at all, though it is marked lazy on the JPA entity. No LazyInitializationException, and no particular issue when merging the entity modified in Flex in the JPA persistence context. GraniteDS has transparently serialized and deserialized the Hibernate internal state of the collection back and forth, thus making the data coming from Flex appear exactly as if it came from a Java client.

So let's try to implement the editing of the list of books in the update form. We don't have to change anything in the service, Hibernate will take care of persisting the collection because of the cascading option we selected. On the Flex side, we can use an editable List (note that there is no built-in editable Spark List in Flex 4, so we use a custom ItemRenderer inspired from this blog post, see the full sources attached), and add this :

<mx:FormItem label="Books" includeIn="edit">
<s:HGroup>
<s:List id="lBooks" dataProvider="{author.books}" labelField="title" width="300" itemRenderer="BookItemRenderer"/>
<s:VGroup>
<s:Button label="Add" click="addBook()"/>
<s:Button label="Remove" enabled="{Boolean(lBooks.selectedItem)}" click="removeBook()"/>
</s:VGroup>
</s:HGroup>
</mx:FormItem>
And the corresponding script actions :
private function addBook():void {
var book:Book = new Book();
book.author = author;
author.books.addItem(book);
lBooks.selectedIndex = author.books.length-1;
}

private function removeBook():void {
author.books.removeItemAt(lBooks.selectedIndex);
}
The Tide framework automatically takes care of initializing the collection when needed, you just have to bind it to any Flex data component such as List. Once again all the usual boilerplate code necessary to deal with data and collections has completely disappeared.

 

Published at DZone with permission of its author, William Draï.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Nuttapong Maneenate replied on Sat, 2011/02/12 - 9:59am

Why step mvn install it error ? [ERROR] Failed to execute goal org.sonatype.flexmojos:flexmojos-maven-plugin:3.8 :compile-swf (default-compile-swf) on project gdsspringflex-flex: Error compilin g! -> [Help 1]

Lucas Marino replied on Tue, 2011/02/22 - 11:05pm

Thank you for your work is excellent. I am interested in your articles and I would ask permission to translate into Spanish and post mentioning the source of course. If you agree email me. My email is marinoluck@gmail.com or answer me in this article. Thank you very much. Lucas Marino

Arthur Vernon replied on Wed, 2011/02/23 - 5:22pm

With the Data Push part of the tutorial, what facilities exist within the framework to identify the source of an update to avoid adding a new author twice to the list? As it currently stands, when I take this code "as is" and add a new author, the code 1. Adds the author to the authors list at creation time. 2. Adds it a second time as a result of the observer. I suppose the trusting solution is to maintain the list only via the observer.

William Draï replied on Thu, 2011/02/24 - 3:55pm in response to: Nuttapong Maneenate

@Nuttapong : Not sure what happens here, maybe try to reset you maven repo.

@Arthur : Right, the recommended solution is to keep only the observer. Unfortunately there is currently no way of knowing where the update comes from (local, remoting or push).

Chris Jansen replied on Fri, 2011/02/25 - 11:24am

I had to specify a version for maven-jetty-plugin to get it to run inside of Spring STS IDE.  Great tutorial too - thank for writing it up!

Richard Van Der Laan replied on Tue, 2011/03/08 - 6:09am

Hi there,

I am unable to extract the extended data from the validation exception, as described in the example:

function(event:TideFaultEvent):void {

    if (event.fault.faultCode == 'Validation.Failed') {        // Do something interesting, for example show the first error message-->        Alert.show(event.fault.extendedData.invalidValues[0].message);    }}
 The problem is that fault (type mx.rpc.Fault) has no extendedData property and the property cannot dynamically be accessed. Is this due to an API change? Or am I doing something wrong?

Deepak Srivastav replied on Fri, 2011/03/11 - 8:01am

my entity attached with a form is not updated in the database ie the effect of updating entity in flex side does not update the entity the method of service is called each time but the entity remained same .I will greatly appreciate any kind of help

iain starks replied on Fri, 2011/04/01 - 5:43am

I failed to do jetty:run-war after creating the archetype, getting a java.lang.NoSuchMethodError: javax.persistence.spi.PersistenceUnitInfo.getValidationMode()Ljavax/persistence/ValidationMode; The version ins persistence-api 1.0 doesn't have this method, but it was being pulled in as well from the hibernate-jpa-2.0-api-1.0.0.Final.jar So I removed the dependency: javax.persistence persistence-api 1.0 From the java pom and re-build and that seemed to get me going.

Eric Be replied on Mon, 2011/05/02 - 11:12pm

Hi, Great article! I was wondering if there was a way to access your completed exmaple/application to see the entire sources of everything. I can't find a d/l link anywhere in the tutorial and the archetype project doesn't include any of your more detailed classes/interfaces/etc. Thanks! Eric

Lou Leal replied on Mon, 2011/07/18 - 3:18pm in response to: Richard Van Der Laan

According to this http://www.graniteds.org/jira/browse/GDS-846 this error was fixed in 2.2.-_SP2 but I updated my pom to this version of granite and the same error occurs. I guess it didn't get fixed...

Dmitry Kv replied on Tue, 2011/08/09 - 11:03am in response to: Lou Leal

should be
(event.extendedData.invalidValues[0].message);

Khent Johnson replied on Fri, 2011/09/02 - 2:41pm

Hello! Just got in here. I used the most recent release of GraniteDS and same with Eric I'd like to access your complete example and if possible could anyone share the links of guide related to the topic. I'll be glad and thankful to any response. Cheers! GAR Labs

Sanjay Patel replied on Wed, 2012/08/29 - 7:53am

Thanks. This is very nice article to get started. 

Can you please add or guide how to get flexmojos wrapper goal working? 

Gary Huitson replied on Wed, 2012/09/12 - 10:03am in response to: Richard Van Der Laan

I think theres a mistake in the example code

The line should read...

Alert.show(event.extendedData.invalidValues[0].message);  

The extendedData is a property of event not event.fault 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.