EJB persistence, part three

 

Summary

In the final part in our series on EJB persistence, we wrap up the techniques involved in an example application.

Events

Echelon 2012
June 11 and 12, 2012

University Cultural Centre, National University of Singapore

Startup Asia Jakarta 2012
June 7 and 8, 2012

12th Floor, Annex Building, Wisma Nusantara Complex, Jl. M.H. Thamrin No. 59 Jakarta 10350, Indonesia

MMA Forum Singapore
April 23-25, 2012

Grand Hyatt Singapore



In the previous parts we've covered the basic elements of persisting with JPA in Java SE. Now, we'll look at an example application and expand on how you can use JPA in your development.

First though, let's look at the requirements for the example application, downloadable here. It's all about managing licences. There are a number of applications, each application has several versions, each version in turn has one or more licences associated with it. There's also a set of users, who can be associated with any of those licences. We want to create an application which can manage all of those elements.

Let's start with the entities. They are in their own package rather than mixed with the application code. It is worth applying this discipline; in a larger project you could handle entities as a project in their own right making them easier to reuse in other projects. We have created four entities, Application, Version, Licence and User, so let's look at the highlights of each entity.

In the Application class, we have a one to many relationship with the Version class. Here's part of the Application method; we've skipped the id and name properties for these as they are similar to what we've previously covered.

@Entity
public class Application {
    ...
    private List<Version> versions=new ArrayList<Version>();
    ...
    @OneToMany(mappedBy="application",cascade=CascadeType.ALL)
    public List<Version> getVersions() {
      return versions;
    }
    ...
}

We covered the mappedBy parameter last month. What is new is the cascade parameter. The cascade controls the ability of the persistence engine to let operations flow down affecting other tables in the database. By default, there is no cascading so changes to a collection require you to manage the contents of the collection explicitly. Examining the other CascadeType values reveals the covered operations: ALL, PERSIST, MERGE, REMOVE, REFRESH. A setting of CascadeType.PERSIST for example, would cascade only persisting objects, so if a new Version instance were added to the version list, then updating the Application instance would cascade to save the new Version in the underlying database. CascadeType.MERGE applies the same rule to updates and CascadeType.REMOVE does the same for removals from the collection. CascadeType.REFRESH cascades the rereading of entities from the database; we'll cover refreshing later.

Moving into the Version class, we have the inverse side of the @OneToMany to Application and another cascading collection, this time for a set of licences.

@Entity
public class Version {
    ...
    private Application application;
    private Set<Licence> licences=new HashSet<Licence>();
    ...
    @ManyToOne
    public Application getApplication() {
       return application;
    }
    ...
    @OneToMany(mappedBy="version",cascade=CascadeType.ALL)
    public Set<Licence> getLicences() {
       return licences;
    }
    ...
}

Moving down the chain, we get to the Licence class. It's here that the cascade ends.

@Entity
public class Licence {
    ...
    private Version version;
    private Set<User> users=new HashSet<User>();
    ...

We still have a collection to represent the set of users. The mapping to the version is handled with a @ManyToOne annotation.

@ManyToOne
public Version getVersion() {
    return version;
}

Now we get to the most important mapping in this example; many licences can refer to many users, so we use the @ManyToMany annotation to denote this.

@ManyToMany
@JoinTable(name="LicenceUsers",
    joinColumns={@JoinColumn(name="licence_id")},
    inverseJoinColumns={@JoinColumn(name="user_id")})
public Set<User> getUsers() {
    return users;
  }
}

The @JoinTable annotation allows us to take control of the @ManyToMany and override its defaults. The name parameter is the name of the table we want to be created to hold the many to many mapping. The joinColumns parameter allows us to then set one side of the join. You may wonder why the braces around the argument we are giving it; the argument type is an array, so despite there being only one value, we still need the braces. The value within that is an @JoinColumn annotation which sets the name for the column in the database. The same syntax applies in the inverseJoinColumns parameter, this time though for the other side of the many-to-many relationship.

Finally we get to the User class. For this example, we are assuming that users are uniquely identified through their user name. We can therefore use that as an identifier rather than using @GeneratedId to generate a unique ID value which we have used in the other entity classes.

@Entity
public class User {
    private String userName;
    private List<Licence> licences=new ArrayList<Licence>();
    public User() {}
    @Id
    public String getUserName() {
       return userName;
    }
    ...

Now we can look at creating a mapping to the licences, the other side of the ManyToMany relationship, which is of course an @ManyToMany.

@ManyToMany(mappedBy="users")
public List<Licence> getLicences() {
return licences;
}
...
}

Here we use the mappedBy parameter to point at the users property in the Licence class. This allows the persistence engine to use the @JoinTable we specified in Licence.

That's the entities; now we move on to how we are going to manipulate them. We are writing this example to be targeted at a Java Standard Edition platform, so as in the previous articles we can create a manager class and instantiate the EJB/JPA layer from that class. Here, it's the LicManStore.java class which we'll create as a singleton.

public class LicManStore {
private EntityManagerFactory emf;
private EntityManager em;
private static LicManStore myInstance;
public static LicManStore getStore() {
    if(myInstance==null) {
      myInstance=new LicManStore();
       }
      return myInstance;
}
private LicManStore() {
    emf=Persistence.createEntityManagerFactory("appman",
      new Properties());
    em=emf.createEntityManager(PersistenceContextType.EXTENDED);
}
...

The constructor has one obvious difference from previous examples; the use of PersistenceContextType.EXTENDED. By default, EntityManagers are created with a PersistenceContextType of TRANSACTION. What this means is that entities are managed only when there's an active transaction in progress. As soon as the transaction ends, the entities become detached from the entity manager which we can then discard. The EXTENDED context type says that that detaching doesn't happen, keeping the entities managed beyond the end of a transaction. This means for example, you don't need to worry if a collection is lazily fetched, because the entity manager is available to do the required fetching. We still need to get the EntityTransaction when we want to persist and update/merge entities or remove entities from the database, for example when we want to save a new Application instance;

public void saveApplication(Application a) {
   EntityTransaction tx=em.getTransaction();
   tx.begin();
   em.persist(a);
   tx.commit();
}

If you look in the LicManStore source, you'll find only save, update and delete operations for Application and only save and delete for User, Licence and Version have no methods. They haven't been forgotten, but aren't needed. Remember that we set the cascade on the relationships between Application and Version and Licence. This means all we have to do is update the owning Application and all the required operations for Version and Licence are done. If you look in the Controller.java code, you'll see this in action. First let's look at creating an Application:

void addApplication(String appname) throws UpdateException {
if(licmanStore.getApplication(appname)!=null)
    throw new UpdateException("An application "
      + appname + " already exists");
Application app=new Application();
app.setApplicationname(appname);
licmanStore.saveApplication(app);
populateApplications();
ui.setSelectedApplication(app);
}

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.

Talkback

Add your opinion

In order to post a comment, you need to be registered. (Sign In or register below)

Post your comment

ZDNet Asia Live

Malaysian organizations are apathetic about information security and fail to realize they are potentially under... http://t.co/XeuvbXrs

Big data acquisitions pave way to fast, effective innovation - ZDNet Asia News http://t.co/vDZpl0lu

"Big data acquisitions pave way to fast, effective innovation" including @Vivisimo_Inc (client) in @ZDnetAsia http://t.co/yNSdPqbb

Homegrown smartphone OSes gaining favor in China: 59 Jakarta 10350, Indonesia Locally-made mobile operating syst... http://t.co/BruP98Es

RT @MDMGeek: Big data acquisitions pave way to fast, effective innovation - ZDNet Asia http://t.co/ky8YgPAn #Bigdata #analytics via @ciropuglisi

Integration, focused investments to propel Windows Phone http://t.co/6JkDa9sB

RT @AsianFashionLaw: Malaysia offers some manufacturing benefits over China http://t.co/bMquIFiX

Acquisitions in the Big Data market increasingly important to enterprises… http://t.co/Br4BkXyZ

Experience trumps content in apps monetization http://t.co/iaCY5ebX

Malaysia offers some manufacturing benefits over China http://t.co/bMquIFiX

RT @MDMGeek: Big data acquisitions pave way to fast, effective innovation - ZDNet Asia http://t.co/ky8YgPAn #Bigdata #analytics via @ciropuglisi

Thats it.Im digging up an old bus plan i wrote around acquisition of #bigdata talent. http://t.co/gpkha5A1 Any investors want2 read/discuss?

Integration, focused investments to propel Windows Phone: By Kevin Kwang , ZDNet Asia on May 23, 2012 (2 mins ag... http://t.co/aaa0Cb73

Homegrown smartphone OSes gaining favor in China http://t.co/lOBVp1T6

Homegrown smartphone OSes gaining favor in China: 59 Jakarta 10350, Indonesia Locally-made mobile operating syst... http://t.co/gHypbdIY

So much as we know , MTK6575 extremely integrated frequency1GHz ARM Cortex-A9 processor, the superiority of 3G / HSPA Modem, and help the...

1 day ago by y15822137359 on 5 SaaS adoption speed bumps to avoid

I reckon your view: "CRM is strategy, not software", if a company replicating the approach uses in ERP implementation into CRM, what they...

2 days ago by wykoong on Gartner: Mobile CRM gives better ROI than social

This video will teach you about the Excel fill handle but also provide you with a workook to download... http://www.youtube.com/watch?v=...

3 days ago by TradeBrother on A quick fill handle trick for Microsoft Excel

waiting...

5 days ago by eapete on What should count in a company's market value?

Boy, you've opened a can of worms now.

Wait for the rants & raves.

5 days ago by eapete on What should count in a company's market value?

I was puzzling before this whether to replicate the success formula we executed for a financial institute, and come out with a standard s...

5 days ago by wykoong on Drop the egos, copy ideas, then innovate