详解EJB 3.0是如何简化应用程序的开发
实体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
("s