Nothing remarkable there; the populateApplications method retrieves all the applications and fills in the UI's model for the displayed list, then we set the newly persisted Application to be selected in that list. Now let's take the process off adding a new Version to an Application. Here, currentApplication is the currently selected Application instance and it is called with a string to be the new version name:

void addVersion(String versionname) {
if(currentApplication==null) return;
Version ver=new Version();
ver.setVersionname(versionname);
ver.setApplication(currentApplication);
currentApplication.getVersions().add(ver);
licmanStore.updateApplication(currentApplication);
populateVersions();
ui.setSelectedVersion(ver);
}

All we do is create a new Version, set its name, set that its parent is the currentApplication, and then add the version to the currentApplication's version list. To commit this to the database, all that we do is update/merge the currentApplication. There is a problem though with this code; the newly created version isn't selected in the list. If the entity manager has taken care of the cascading, the new Version instance that we added is not managed, it's merely a shell, unlike when we saved the Application in addApplication() where the object became managed explicitly. The code here won't work because when we try and select the version in the list at the end of the method, the shell of Version we started with doesn't exist in the list, only a different managed Version instance based on it. The fix is simple, just retrieve the managed version. A getVersion method in the store takes care of the retrieval:

Version getVersion(Application app,String name) {
try {
    Query q=em.createQuery("select ver from Version as ver
      where application=:app and versionname=:name");
    q.setParameter("app",app);
    q.setParameter("name",name);
    return (Version)q.getSingleResult();
  } catch (NoResultException nre) {
     return null; }
}

The getSingleResult method for a Query says return just one result, and it throws exceptions when none or more than one result is found. Here, we translate the lack of a result to returning null. Now we can change the Controller's addVersion method to use that and for good measure, we can also use it to check we aren't creating duplicate names, throwing an exception when we do:

void addVersion(String versionname) throws UpdateException {
if(currentApplication==null) return;
if(licmanStore.getVersion(currentApplication,
    versionname) != null)
      throw new UpdateException("Version "
        + versionname + " already exists");
...
populateVersions();
ver=licmanStore.getVersion(currentApplication,versionname);
ui.setSelectedVersion(ver);
}

And now, the newly created version is automatically selected. As an exercise for the reader, we've left a similar bug in the code for the Licence creation ready for fixing.

One thing to remember when working with an extended persistence context is that you do need to keep persisted entities in sync. There are two ways to approach this. For example when we assign a User to a Licence, we have to remember to update the User's licence collection in memory as well as adding the User to the Licence's users collection. In the addUsersToLicence method in Controller, we make sure that we add to both the User and the Licence lists:

void addUsersToLicence(List<User> u) throws UpdateException {
if(currentLicence==null) throw
    new UpdateException("No licence selected");
currentLicence.getUsers().addAll(u);
for(User ut:u) ut.getLicences().add(currentLicence);
licmanStore.updateApplication(currentApplication);
populateLicenceUsers();
}

It may of course not be practical to do this; the other approach is to ask the entity manager to refresh a managed object to bring it into sync with the database. To do this we add a refresh method to the LicManStore class:

void refresh(Object o) { em.refresh(o); } and just refresh the User instances from the database instead:

void addUsersToLicence(List<User> u) throws UpdateException {
if(currentLicence==null) throw
    new UpdateException("No licence selected");
currentLicence.getUsers().addAll(u);
licmanStore.updateApplication(currentApplication);
for(User ut:u) licmanStore.refresh(ut);
populateLicenceUsers();
}

If we'd been using a default transaction context for the entity manager, then the entity manager would be typically be freshly created and we wouldn't have lingering managed objects which haven't been recently refreshed from the database.

Finally, let's look at the "Licence report" which lists all the licences a user has; it is displayed when you double-click on a user in the user list. This is done entirely by traversing the relationships to get the application name and version name from the User class's list of Licences:

...
StringBuilder sb=new StringBuilder();

for(Licence l:user.getLicences()) {
    sb.append(l.getVersion().getApplication().getApplicationname());
    sb.append(" ");
    sb.append(l.getVersion().getVersionname());
    sb.append(" ");
    sb.append(l.getLicenceKey());
    sb.append("\n");
} ...

No apparent database access occurs as the entity manager takes care of pulling in the appropriate Licence, Version and Application classes on them being accessed, thanks to the extended persistence context. This of course comes with a cost, particularly in terms of memory usage over the lifetime of the application caching accessed objects, which is why you should always consider using the transaction context and creating entity managers on demand when planning how to use JPA.

This wraps up our look at using JPA in Java Standard Edition. JPA is not just for Java SE, its roots being in Java Enterprise Edition. The same entity classes we've used here can be used in an enterprise application with no changes; the actual changes occur in how you acquire an entity manager and where that in turn gets its configuration. And that architecture portability is where JPA scores highly. Skills learned with Java SE are also portable the Java EE.

You can download the source code for this article here.

DJ Walker-Morgan is a consulting developer, specialising in Java and user-to-user messaging and conferencing.

Do you need help with Java, C, or C++? Gain advice from Builder AU forums

Related links

Comments

1

Ole Martin S&Atilde;&cedil;rli - 21/02/08

about @ManyToMany:

I have a relational database, with a table 'user'.
this 'user' needs to relate to many other 'user'-entities.

I have searched the web for days now without finding one example where ONE table has a many to many relation to itself.

Is this not covered in EJB persitence?

This is the type of relation I use the most, and I cannot think of any other way to model it. It is fairly simple to solve with native sql, but a combination of the two becomes rather hairy..

» Report offensive content

Leave a comment

You must read and type the 6 chars within 0..9 and A..F

* indicates mandatory fields.

1

Ole Martin S&Atilde;&cedil;rli - 21/02/08

about @ManyToMany: I have a relational database, with a table 'user'. this 'user' needs to relate to many other 'user'-entities. I have searched the ... more

Log in


Sign up | Forgot your password?

  • Chris Duckett IE9's H.264 vote killed Ogg

    In a split decision by the judges, the winner of the W3C/WHATWG video codec consensus is H.264, taking home the future of video playback on the internet while loser Ogg goes home with nothing but thoughts of what might have been. Read more »

    -- posted by Chris Duckett

  • Staff Google launches Apps Marketplace

    Google launches and app store, while Mozilla plans to re-write its open-source license. More of this week's news in the Roundup. Read more »

    -- posted by Staff

  • Staff Microsoft showcases new NUIs

    TechFest, Microsoft's internal even took place this week with researchers showcasing some new interfaces the company is working on. Read more »

    -- posted by Staff

What's on?

  • Optus Deal

    Broadband + home phone + PlayStation®3 in a single package price!