您的位置:首页 > 其它

详解EJB 3.0是如何简化应用程序的开发

2005-06-06 12:27 435 查看
详解EJB 3.0是如何简化应用程序的开发

在读过有关EJB 3.0如何简化应用程序开发的文章和邮件后,我决定对EJB 3.0来做一个实验看是否真像所声称的那样。我决定采用J2EE 1.4教程中一个众所周知的演示程序。
我所用RosterApp应用程序有三个实体beans(LeagueBean,TeamBean,PlayerBean),一个会话bean(RosterBean),三个数据传递对象(LeagueDetails,PlayerDetails,TeamDetails),一个调试工具类和一个客户类。我把这些bean转换、迁移、或者转变为基于EJB 3.0的应用程序。
迁移该应用程序的步骤如下:
实体beans迁移
数据传递对象(DTOs)迁移
会话beans迁移
客户类迁移
实体bean的迁移
我从实体bean的迁移(LeagueBean,TeamBean,PlayerBean)着手。不是采用现存的bean并将抽象的方法转换为带有注释(译者注:这里的注释是单词annotation,后面没有特别说明时都指这个词)的getter和setter方法,而是对RosterApp 的表实施反向工程,这些数据库表来自一个作为EJB 3.0的实体bean的Oracle数据库。结果,我得到三个带有缺省注释集的简单的POJO(League,Player,Team)。有了这些POJO,剩下的工作只是为Player与Team之间的多对多的关系添加注释。这些注释如下所示:

//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;
}

当所有的O/R映射都迁移成POJO中的注释后,下一项任务就是将带有EJBQL的finder方法从EJB 2.1中迁移到新的POJO中。这些finder方法大多数是为Player bean定义的。EJB 3.0提供NamedQueries注释将单个的NamedQuery对象组合在一起。我采用了现存的应用程序中的所有EJB QL,并生成一个如下所示的NamedQueries 注释。
我没有对原有EJBQL做任何修改,既没有优化,也没有利用EJB3.0标准中提供的EJBQL新特性。

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")
}
)

映射和finder几乎涵盖了实体bean迁移90-95%的工作量。剩余的部分是对Team执行添加与删除操作的ejbSelect语句和方法。这些方法已经被简化了。下表显示了一个方法迁移前后的代码。EjbSelect方法被作为Session bean中的NamedQuery迁移,详见后述。

// remove operation on Player before
//migrationpublic 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 migrationpublic
void dropPlayer(Player player)
{
Debug.print("TeamBean dropPlayer");
getPlayers().remove(player);
}

数据传递对象的迁移
RosterApp的下一个逻辑层是数据传递对象(DTOs)。EJB 3.0中不再需要DTOs,因为实体bean是基于POJO的,只要实现了java.io.Serializable接口,就可以直接在客户层与业务层传递。现存的Roster 应用程序是使用DTOs在客户层与会话bean之间传递Teams、Player和Leagues数据集合的。
在EJB 3.0持久层规范中,有一个新的EntityManager API,可用于创建、删除、查找和查询实体,使用这个API从持久层上下文中挂接(Attach)和断开(Detach)对象非常简洁。EntityManager中的合并操作可将被断开实体的状态应用到被EntityManager管理的持久性实体。
基于EJB 3.0的Roster应用程序不要求现成的DTO包,但是我必须确保Team、League和Player POJOs实现java.io.Serializable接口。我还必须去掉像getPlayersofTeamCopy这样的额外方法,这些方法在EJB 2.1应用程序中做着在DTO和实体bean之间管理数据的烦琐工作。在消除额外的负担后,我必须简化到处使用DTO的会话bean(RosterApp)中的业务方法。示例迁移代码显示在下表中。

//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();
}

会话bean的迁移
在清除DTO后的任务,就是迁移会话bean(RosterBean)。首先,我必须删除home接口和清除remote接口,使之不扩展EJBObject。然后,bean类和接口必须用@Stateless和@Remote注释进行标注。EJB 2.1中现成的RosterBean(会话bean)有许多与实体bean Team、League和Player交互的方法。移植练习的大部分工作只是通过使用EntityManager API简化业务方法、为ejbSelect方法创建NamedQueries,并使这些方法不与已被删除的DTO交互。

//code using DTOspublic 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 Beanpublic 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 querypublic 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();
}

客户类迁移
一旦完成了会话bean部分的工作,我们就可着手清理客户端代码。主要的不同点是lookup代码,其次,要确保业务方法的返回值直接是POJO而不是DTO,也需要少量的代码修改。

//client code before migrationpublic
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 migrationpublic
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();
}
}

现存的EJB 2.1与新的EJB 3.0应用程序另一个大的不同点是描述器文件的数量。现存的应用程序有大量的部署描述器文件而新的应用程序则清除了除application-client.xml和application.xml文件之外的所有描述器文件。

EJB 2.1 EJB 3.0
Number of Java Files 17 7
Number of XML Files (Descriptors) 9 2

我使用一种能算出非注释(译者注:这里指nocommented)行并且非空行的名叫numlines的工具,只有非注释且非空的行被添加到旧的和新的应用程序中。EJB 2.1程序中XML文件的行数统计是根据教程中推荐的部署步骤计算的。
结论:
EJB 3.0的确简化了实体和会话bean的开发工作。使开发变得容易起来的原因是使用了简化的模型、并且采用了广为人知的POJO和接口之类的工件(Artifacts)。新的EntityManager API是一个大的增强,我可以相当容易地改变业务方法并且不需要去读规格说明书(Spec)。
还有一些其他的优雅特性,比如利用数据库序列(Database Sequence)的能力,不过为了保持现有EJB 2.1程序与新的EJB 3.0程序之间更多的相似性,我没有做这个变更。当前虽然规范这么说,但还看不到对本地(Native)SQL查询的任何支持。
我更喜欢利用Native SQL语句的查询而不是EJBQL,因为,数据库的可移植性对我来说不是要优先考虑的因素。尽管EJB规范看来是在朝正确的方向改进,我还是觉得EJB 3.0需要更多的工具支持,以便更多的普通开发人员能够"染指"并在EJB 3.0规范上进行开发。
尽管任何标准的集成开发环境都支持JDK5,是一个不错的开局,我还是认为,应该有更好的工具去支持复杂的映射(如:多对多)、NamedQueries的就地检查和立即反馈,不要等到部署(才发现问题)。
应用程序的维护是一个不容忽视的问题,因为一个应用程序在开发阶段结束后还有若干年的生命周期。所有使应用程序开发变得简单的特征也将在应用程序的维护阶段得到回报。总之,我愿意推荐开发者们从一个全新的、不带偏见的视角审视EJB3.0标准,检验一下该标准的新内容,实际使用一下已经公开的那些EJB 3.0容器(在本练习中,我使用的是Oracle EJB 3.0容器)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: