http://www.jroller.com/comments/raghukodali/Weblog/does_ejb_3_0_really
Does EJB 3.0 really make application development easy ?
(Download the PDF version of this article)
After reading through articles and posts regarding how EJB 3.0 is going to make application development simpler, I decided to give EJB 3.0 a spin and see if the claims are true.
I decided to take a publicly available demo application which is part of the J2EE 1.4 tutorials. I took the RosterApp application which has three Entity beans (LeagueBean, TeamBean, PlayerBean), one Session bean (RosterBean), three Data Transfer Objects (LeagueDetails, PlayerDetails, TeamDetails), a Debug utility class and a Client class. I converted/migrated/ported these beans and classes to an EJB 3.0 based application.
I migrated the application with the following steps:
Entity Beans
Data Transfer Objects (DTOs)
Session Bean
Client
and gave my final conclusion.
Entity Bean Migration
I started with the Entity bean migration (LeagueBean, TeamBean, PlayerBean). Instead of taking the existing beans and converting the abstract methods to getter and setter methods with annotations, I reverse engineered the RosterApp tables, from an Oracle database as EJB 3.0 Entity beans. Net result, I got three simple POJOs (League, Player, Team) with a set of default annotations. Once I got the POJOs, all I had to do was add annotations for ManyToMany relationship between Player and Team. The annotations looked as follows:
//Team POJO
ManyToMany(cascade=PERSIST,fetch=EAGER)
AssociationTable(table=@Table(name="TEAM_PLAYER"), joinColumns=@JoinColumn(name="TEAM_ID", referencedColumnName="ID"), inverseJoinColumns=@JoinColumn(name="PLAYER_ID", referencedColumnName="ID"))
public List getPlayers() { return players; } |
// Player POJO // players is a List in the Team POJO
@ManyToMany(mappedBy="players", fetch=EAGER)
public List getTeams() { return teams; } |
Once all the O/R mappings are migrated as annotations in the POJOs, the next action item was migrating a bunch of finder methods with EJBQL from EJB 2.1 to new POJOs. Most of these finder methods were defined for the Player bean. EJB 3.0 provides the NamedQueries annotation to group together individual NamedQuery objects. I took all the EJB QL from the existing application, and created a NamedQueries annotation which looked as follows.
No changes were made to the EJBQL, either to optimize or make use of any new EJBQL features in the EJB 3.0 specification.
NamedQueries ({
NamedQuery(name="findAll",queryString="SELECT OBJECT(p) FROM Player p"),
NamedQuery(name="findByCity",queryString="SELECT DISTINCT OBJECT(p) FROM Player p, in (p.teams) as t where t.city = :city"),
NamedQuery(name="findByHigherSalary",queryString="SELECT DISTINCT OBJECT(p1)FROM Player p1, Player p2 WHERE p1.salary > p2.salary AND p2.name = :name "),
NamedQuery(name="findByLeague", queryString=" select distinct object(p) from Player p, in (p.teams) as t where t.league = :league"),
NamedQuery(name="findByPosition", queryString=" select distinct object(p) from Player p where p.position = :position"),
NamedQuery(name="findByPositionAndName",queryString=" select distinct object(p) from Player p where p.position = :position and p.name = :name"),
NamedQuery(name="findBySalaryRange",queryString="select distinct object(p) from Player p where p.salary between ?1 and ?2"),
NamedQuery(name="findBySport", queryString="select distinct object(p) from Player p, in (p.teams) as t where t.league.sport = ?1"),
NamedQuery(name="findByTest", queryString=" select distinct object(p) from Player p where p.name = ?1"), @NamedQuery(name="findNotOnTeam",queryString=" select object(p) from Player p where p.teams is empty") }) |
Mappings and finders covered almost 90-95% of the Entity bean migration. The remaining part was ejbSelect statements and methods that perform add and remove operations on Team. These methods had to be simplified. The following table shows one of the methods before and after migration. ejbSelect methods are migrated as NamedQuery in the Session bean which is discussed in the latter part of this article.
// remove operation on Player before //migration
public void dropPlayer(Player player) { Debug.print("TeamBean dropPlayer"); try { Collection players = getPlayers(); players.remove(player); } catch (Exception ex) { throw new EJBException(ex.getMessage()); } } |
//remove operation after migration
public void dropPlayer(Player player) { Debug.print("TeamBean dropPlayer"); getPlayers().remove(player); } |
Data Transfer Objects Migration
The next layer in the RosterApp were Data Transfer Objects (DTOs). EJB3.0 eliminates the need for (DTOs), as Entity beans are based on POJOs and they can be directly transferred between client tier and business tier as long as they implement java.io.Serializable. The existing Roster application was using DTOs to transfer Teams, Players and Leagues data collections between the client and Session bean.. The new EntityManager API in the EJB 3.0 persistence specification, which is used to create, remove, find and query entities, works nicely to attach and detach objects from the persistence context. The merge operation in the EntityManager allows for the propagation of state from detached entities onto persistent entities managed by the EntityManager. Roster application based on EJB 3.0 didn't require the existing DTOs baggage, but I had to make sure that the Team, League and Player POJOs implement java.io.Serializable. I also had to get rid of extra methods like getPlayersofTeamCopy, which were doing the grunt work of managing data between DTOs and Entity beans in the EJB 2.1 application. On top of eliminating the extra overhead, I had to simplify the business methods in the session bean (RosterApp) that were using DTOs all over the place. Sample migrated code is shown in the following table:
//code before migrating to EJB 3.0
public List getTeamsOfLeague(String leagueId) { Debug.print("RosterBean getTeamsOfLeague"); ArrayList detailsList = new ArrayList(); Collection teams = null;
try { LocalLeague league = leagueHome.findByPrimaryKey(leagueId);
teams = league.getTeams(); } catch (Exception ex) { throw new EJBException(ex.getMessage()); }
Iterator i = teams.iterator();
while (i.hasNext()) { LocalTeam team = (LocalTeam) i.next(); TeamDetails details = new TeamDetails(team.getTeamId(), team.getName(), team.getCity());
detailsList.add(details); }
return detailsList; } |
//code after migrating to EJB 3.0
public List getTeamsOfLeague(String leagueId) { Debug.print("RosterBean getTeamsOfLeague"); League l = (League)getEntityManager().find("League", leagueId); return l.getTeamList(); } |
Session Bean Migration
After eliminating the DTOs, I migrated Session bean (RosterBean). To start with I had to remove the home interface and clean up the remote interface, so that it does not extend EJBObject. After that the bean class and the interface had to be tagged with Stateless
and Remote annotation. Existing RosterBean (Session Bean) in EJB 2.1 has number of business methods which interact with Team, League and Player Entity beans. The big chunk of the porting exercise was mere simplification of the business methods to use the EntityManager API, creating NamedQueries for ejbSelect methods, and making the methods not interact with DTOs that had already been removed.
//code using DTOs
public Player getPlayer(String playerId) { Debug.print("RosterBean getPlayer");
PlayerDetails playerDetails = null;
try { LocalPlayer player = playerHome.findByPrimaryKey(playerId);
playerDetails = new PlayerDetails(playerId, player.getName(), player.getPosition(), player.getSalary()); } catch (Exception ex) { throw new EJBException(ex.getMessage()); }
return playerDetails; } |
//code using the new EntityManager //API
public Player getPlayer(String playerId) { Debug.print("RosterBean getPlayer"); return (Player)em.find("Player",playerId); } |
.
// code using DTOs and calling //ejbSelect methods in the Entity Bean
public List getLeaguesOfPlayer(String playerId) { Debug.print("RosterBean getLeaguesOfPlayer");
ArrayList detailsList = new ArrayList(); Collection leagues = null;
try { LocalPlayer player = playerHome.findByPrimaryKey(playerId);
leagues = player.getLeagues(); } catch (Exception ex) { throw new EJBException(ex.getMessage()); }
Iterator i = leagues.iterator();
while (i.hasNext()) { LocalLeague league = (LocalLeague) i.next(); LeagueDetails details = new LeagueDetails(league.getLeagueId(), league.getName(), league.getSport());
detailsList.add(details); }
return detailsList; } |
//Code after migration, no DTOs and //ejbSelect migrated as inline query
public List getLeaguesOfPlayer(String playerId) { Debug.print("RosterBean getLeaguesOfPlayer");
Query query = em.createQuery("select distinct t.league from Player p, in (p.teams) as t where p = ?1"); query.setParameter(0,playerId); return query.getResultList();
} |
Client Migration
Once the SessionBean part was done, it was time to clean up the client code. The main difference is the lookup code, and minor changes that were required to make sure that return values of business methods are direct POJOs instead of DTOs.
//client code before migration
public static void main(String[] args) { try { Context initial = new InitialContext(); Object objref = initial.lookup("java:comp/env/ejb/SimpleRoster");
RosterHome home = (RosterHome) PortableRemoteObject.narrow(objref, RosterHome.class);
Roster myRoster = home.create();
insertInfo(myRoster); getSomeInfo(myRoster); getMoreInfo(myRoster); System.exit(0); } catch (Exception ex) { System.err.println("Caught an exception:"); ex.printStackTrace(); } } |
//client code after migration
public static void main(String[] args) { try { Context initial = getInitialContext(); Roster myRoster = (Roster)initial.lookup("java:comp/env/ejb/SimpleRoster");
insertInfo(myRoster); getSomeInfo(myRoster); getMoreInfo(myRoster); System.exit(0); } catch (Exception ex) { System.err.println("Caught an exception:"); ex.printStackTrace(); } } |
Another big difference between the existing EJB 2.1 and new EJB 3.0 application is the number of descriptors. The existing application had number of deployment descriptors and the newer app has completely eliminated all of them except for application-client.xml and application.xml.
|
EJB 2.1 |
EJB 3.0 |
Number of Java Files |
17 |
7 |
Number of XML Files (Descriptors) |
9 |
2 |
|
EJB 2.1 |
EJB 3.0 |
Lines of code |
987 in 17 Java Files |
716 in 7 Java Files |
Lines of code |
792 in 9 XML Files |
26 in 2 XML File |
I used the numlines utility, which gives the line count for uncommented and non-blank lines. Only uncommented and non-blank lines were added for the old and new application. XML files in the case of the EJB 2.1 app were counted based on the deployment steps recommended in the tutorial.
Conclusion
EJB 3.0 definitely simplifies the development of entity and session beans. Ease of use comes from the simplified model, and leveraging well known artifacts like POJOs and interfaces. The new EntityManager API is a big plus; I was able to make the changes to the business methods quite easily and did not require the need of reading the specification. There are other neat features like the ability to make use of database sequences, which I used for one of the POJOs, but backed out the changes to have more similarity between the existing (EJB 2.1)and new (EJB 3.0) application. Currently there doesn't seem to be any support for using Native SQL queries, though the specification seems to say so. I would have loved to make use of those queries instead EJBQL, as database portability isn't a priority for me.While the specification seems to be step in the right direction, I think we should see more tooling support for EJB 3.0, so that more general application developers can get their hands dirty and come up to speed on EJB 3.0. While any standard IDE that has good support for JDK5 will provide a good start, I think there should be better tooling to support complex mappings (like ManyToMany), and facilitate inline or immediate feedback on the validity of NamedQueries instead of waiting for deployment. Maintenance of the applications isn't something that can be ignored, as applications will live for few years after the development cycle. All the features that make the application development easier will also provide returns in application maintenance cycle. All in all, I will recommend that developers take a fresh and unbiased look at the EJB 3.0 specification, by checking out the features and giving the publicly available EJB 3.0 containers ( I used Oracle EJB 3.0 Container for this exercise) a spin.
References and Links
Software used for EJB 2.1 application (as recommended in the J2EE 1.4 tutorial)
Software used for EJB 3.0 application (Oracle EJB3.0 Container)
Source files for the migrated application
EJB 3.0 specification
Download the PDF Version of the article