Rise in <b>Chinese</b>-funded acquisitions could trigger more hurdles http://t.co/0pXBS1HR
7 minutes ago by GeorgeHAllenGA on twitter
ZDNet is available in the following editions:
In the final part in our series on EJB persistence, we wrap up the techniques involved in an example application.
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.
Rise in <b>Chinese</b>-funded acquisitions could trigger more hurdles http://t.co/0pXBS1HR
7 minutes ago by GeorgeHAllenGA on twitterRise in Chinese-funded acquisitions could trigger more hurdles: By Ellyne Phneah , ZDNet Asia on May 22, 2012 (6... http://t.co/W3SOdw2c
7 minutes ago by MergeAcquire on twitterRT @zdnetasia: CFOs increasingly involved in IT investment decisions. http://t.co/8QrfwOSb
22 minutes ago by 666hellscream on twitterCFOs increasingly involved in IT investment decisions http://t.co/XD1LerFq via @zdnetasia #PrivateCloud #SC2012 #CAPEX
37 minutes ago by HarishAitharaju on twitterRise in Chinese-funded acquisitions could trigger more hurdles. http://t.co/VC3G3m3o
37 minutes ago by zdnetasia on twitterRT @zdnetasia: Rise in Chinese-funded acquisitions could trigger more hurdles. http://t.co/VC3G3m3o
37 minutes ago by wrikent3500 on twitterSo much as we know , MTK6575 extremely integrated frequency1GHz ARM Cortex-A9 processor, the superiority of 3G / HSPA Modem, and help the...
41 minutes ago by y15822137359 on 5 SaaS adoption speed bumps to avoidRise in Chinese-funded acquisitions could trigger more hurdles - ZDNet Asia: Rise in Chinese-funded acquisitions... http://t.co/bZaAQnRL
52 minutes ago by MandAWorldwide on twitterRise in Chinese-funded acquisitions could trigger more hurdles http://t.co/mIsuZjnU http://t.co/erFX4aVv #arcavir
52 minutes ago by V_RaV on twitterhttp://t.co/VNaZtseV Rise in Chinese-funded acquisitions could trigger more hurdles: "Cash r... http://t.co/N0gZZEdR http://t.co/wiqY9ktt
52 minutes ago by RavtachSolution on twitterRise in Chinese-funded acquisitions could trigger more regulatory clearance issues overseas http://t.co/cvLSpTwo #in
52 minutes ago by EllyZDNetAsia on twitterAlibaba seeks $2.3B from shareholders for Yahoo deal. http://t.co/qLRAhRQk
1 hour ago by zdnetasia on twitterCFOs increasingly involved in IT investment decisions. http://t.co/8QrfwOSb
1 hour ago by zdnetasia on twitterOfficial UEFA #EURO2012 app with Orange 2.0 http://t.co/yoAOXTI1 #hotpeopleifollow
1 hour ago by JohnReporter on twitterWhy wouldn't they be?: CFOs increasingly involved in IT investment decisions http://t.co/4gHYrmQy via @zdnetasia
1 hour ago by TheVoxOxCFO on twitterI reckon your view: "CRM is strategy, not software", if a company replicating the approach uses in ERP implementation into CRM, what they...
1 day ago by wykoong on Gartner: Mobile CRM gives better ROI than socialThis video will teach you about the Excel fill handle but also provide you with a workook to download... http://www.youtube.com/watch?v=...
1 day ago by TradeBrother on A quick fill handle trick for Microsoft Excelwaiting...
3 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.
I was puzzling before this whether to replicate the success formula we executed for a financial institute, and come out with a standard s...
4 days ago by wykoong on Drop the egos, copy ideas, then innovateEchelon 2012 - The Awesomer Tech Event in Asia
Echelon 2012 – SEA’s longest running tech startup event goes Awesomer. Catch 50 of Asia’s most promising startups & over 40 international speakers on June 11-12.
Startup Asia Jakarta showcases new product-ready tech startups. Plus: hackathon, exhibition, and speakers. Use promo code CBSi50 for 50% discount.
ZDNet Asia Intelligent Singapore video series
Featuring inteviews with CXOs who define "intelligence" in their markets and reveal how their companies drive business efficiencies through ICT.