| 开发人员:J2EE
结合使用 Oracle TopLink 和 Spring 框架
作者:Lonneke Dikmans(Oracle 融合中间件区域总监)
将 Spring 1.2.8 和 Oracle TopLink 与 Oracle JDeveloper 10g 结合使用以便逐步构建示例应用程序。
2006 年 9 月发布
Spring(用于开发轻型 J2EE 应用程序的流行框架)核心上使用面向方面编程 (AOP) 和依赖性注入。它支持多种框架,包括与对象相关的映射 (ORM) 工具,例如,Oracle TopLink 和 JBoss Hibernate。在本文中,您将学习如何使用 Spring 框架对 TopLink 的支持。
Spring 提供了若干好处:
- 它使用 DAO 模式从业务逻辑提取数据访问代码。
- 它使用 Template 模式实现数据访问的不变量行为。
- 它有一个通用的异常层次结构。
- 它提供集成事务管理,
- 并负责资源管理。
使用 Spring 不会更改底层 ORM 工具的基本内容。这意味着您可以继续以您习惯的方式设计和编码应用程序。
这里,您要将 Spring 1.2.8 和 TopLink 与 Oracle JDeveloper 10.1.3 结合使用来构建一个示例应用程序。首先,您将建立工作区,然后查看示例应用程序。之后,您将检查数据访问代码以及 Spring 配置。测试代码之后,您要将该应用程序部署到 Oracle Containers for Java (OC4J) 运行时。
建立您的工作区
当创建您自己的应用程序时,您要将它基于具有 TopLink、EJB 和 JavaServer Faces (JSF) 的 Web 应用程序模板。但是,对于本例来说,请首先下载并提取示例应用程序,然后将它导入到 JDeveloper(参见图 1):
- 将该 zip 文件提取到您选择的文件夹中。
- 单击绿色加号导入现有应用程序。
- 导航到您将 zip 文件提取到的文件夹。
- 双击 HockeyCoachAssistant.jws 文件。
图 1. 导入现有应用程序。
当您导入该应用程序之后,必须添加库以便使用 Spring 框架并测试该应用程序。由于您使用了 Web 应用程序模板,因此用于 TopLink 和 JSF 的库已经添加到 Model 和 ViewController 项目。表 1 显示了您需要添加的库。
表 1. 每个项目需要的库。
|
项目
|
库名
|
Jar 位置
|
备注
|
|
Model
|
spring128
|
class:[springhome]\dist\spring-dao.jar, spring.jar, spring-orm.jar
src:[springhome]\src
doc:[springhome]\docs\api
|
www.springframework.org |
|
TestModel
|
Spring128Test
|
class:[springhome]\dist\spring-mock.jar
src:[springhome]\src
doc:[springhome]\docs\api
|
www.springframework.org |
|
TestModel
|
Spring128
|
参见 Model 项目
|
参见 Model 项目
|
|
TestModel
|
JUnit Runtime
|
下载适用于 JDeveloper 的扩展
|
使用来自 JDeveloper 扩展中心的扩展
|
|
TestModel
|
dbunit
|
Class:[dbunithome]\dbunit2.1.jar
|
www.dbunit.org |
|
ViewController
|
Spring128
|
参见 Model 项目
|
参见 Model 项目
|
首先添加用于该 Model 项目的库:
- 添加 Spring 作为一个库。
- 下载 Spring Framework 1.2.8
- 用名称 Spring128 创建一个用户定义的库。
- 添加类、源和文档位置
该 Model 项目现在如图 2 所示。
图 2. 编译和运行该 Model 项目所需的库。
接下来,将库添加到 TestModel 项目:
- 添加 spring128 库。
- 添加 spring-mock 库。
- 使用名称 Spring128Test 创建用户定义的库。
- 将 spring-mock-jar 添加到类中。
- 添加 JUnit 库。
- 请使用帮助单中的选项“Check for Updates”来安装 JUnix 运行时。
- 将 JUnit Runtime 库添加到该项目。
- 添加 DbUnit 库。
- 从 www.dbunit.org 下载 DbUnit。
- 创建名为 dbunit 的用户定义库。
- 将 dbunit2.1.jar 添加到该类路径。
- 或者,下载这些源,然后也将它们添加到该库中。
TestModel 项目现在看起来如图 3 所示。
图 3. 编译和运行 TestModel 项目所需的库。
最后,将所需的库添加到 ViewController 项目:
- 添加您为该模型项目创建的 spring128 库。
ViewController 项目现在应该如图 4 所示:
图 4. 编译和运行 ViewController 项目所需的库。
示例应用程序
建立了工作区后,您就可以关注 HockeyCoachAssistant 应用程序了。这个简单的程序记录了一场曲棍球赛中的队伍;用户可以添加、删除或编辑比赛的球队。
该用户界面是一个 Web 应用程序,它包含带有 ADF Faces 组件的 java 服务器页面 (jsp)。启动页面 teams.jsp(参见图 5)。
图 5. 以 faces-config.xml 中定义的方式在应用程序中导航。
该应用程序包含 4 层:模型、数据访问 (DAO)、服务和视图(参见图 6)。本文关注 DAO 层,它是使用 Spring 对 TopLink 的支持的地方。
图 6. HockeyCoachAssistant 应用程序的逻辑视图。
安装应用程序
要显示 TopLink 支持如何用在 HockeyCoachAssistant 应用程序中,必须创建到该数据库的连接并创建模式和表。
- 通过在该 Model 项目的 Database 文件夹中运行 SQL 脚本创建该数据库模式。
- 通过利用诸如 SQLPlus 或 SQLDeveloper 的工具运行文件 compUser.sql 来创建一个称为“comp”的用户。确保作为系统或有权创建用户的另一个用户进行连接。
- 以用户“comp”的身份进行连接,并使用同一工具运行 createTable.sql 脚本。
- 在 JDeveloper 中使用 Connections 选项卡创建数据库连接。
- 创建称为 Competition 的新数据库连接。
- 输入用户名 (“comp”) 和密码 (“comp”)。
- 输入 SID 指向您的数据库。
- 在 sessions.xml 中纠正连接设置(参见图 7)。
- 在 Model 项目的 src\META-INF 文件夹中打开 sessions.xml。
- 在 Structure 窗格中双击称为 localSession 的会话。
- 单击 Login。
- 更新 JDBC 连接 URL 以指向您的数据库。
- 如果需要,更新数据库平台。
图 7. 纠正 localSession 的 URL。
- 纠正 competitionMap 的设置(参见图 7)。
- 单击 TopLink 映射。
- 编辑该连接 URL。
- 如果需要,更改数据库平台。
图 8. 纠正 competitionMap。
- 在 TestModel 项目中编辑 test-toplink-context.xml 文件。
- 双击该文件。
- 更改数据库设置以匹配您的本地安装(参见图 9)。
图 9. 在 test-context.xml 中纠正 JDBC 连接的 URL。
- 检查该安装。
- 重新构建工作区。
- 生成 TopLink 映射状态报告。
- 在 CompetitionDaoTopLinkTest 中运行测试并确保它们失败时都引发 org.springframework.beans.factory.BeanCreationException(参见图 10)。
图 10. 将出现错误,除非您实现 CompetitionDao 接口。
现在您可以实施 CompetitionDao 类了。
数据访问层
数据访问的 Spring 支持包括两个部分:
- 在 J2EE 应用程序中处理通用数据访问元素(包括事务管理、资源管理和异常处理)的 Template 类
- 实施应用程序特定的细节的回调
这适用于 Spring 的所有数据访问支持,包括 TopLink、Hibernate、JDBC、IBATIS 和 JDO。使用 TopLinkTemplate 类有两种方式:您可以使用 Application Context 在应用程序中的每个 DAO 类中注入它,也可以扩展支持类。
我们的示例扩展了 TopLinkDaoSupport 类。这产生了以下数据访问组件结构(图 11):
图 11. 应用程序中的数据访问层结构。
TopLinkCompetitionDao(参见图 11)实施 CompetitionDao 接口,该接口向服务层公开。通过这种方式,数据访问实施细节就可以对应用程序的其他部分隐藏。
- 编辑 TopLinkCompetitionDao 类(参见图 12):
- 允许类扩展 TopLinkDaoSupport
- 导入 springframework.orm.toplink.support.TopLinkDaoSupport 类。
图 12. TopLinkCompetitionDao 的清单。
检索数据。要填充第一页,首先必须使用 findAllTeams 方法检索数据库中的所有小组。如果您不使用 Spring 实施它(例如,通过一个非会话状态会话组件或一个普通的 Java 类),该代码将如清单 1 所示:
清单 1. 不使用 Spring 检索数据。会话管理在代码中。
public List<Team> findAllTeams() {
List<Team> result = null;
Session session = getSessionFactory().acquireSession();
result = (List<Team>)session.executeQuery("findAllTeam", Team.class);
session.release();
return result;
}
由于 Spring 模板处理资源管理,因此您在 TopLinkCompetitionDao 类中只需实施对 executeQuery 的调用。进行会话的获取与释放。(参见清单 2)
清单 2. 使用 Spring 检索数据。资源由 Spring 管理。
public List<Team> findAllTeams() {
return (List<Team>)getTopLinkTemplate().execute(
new TopLinkCallback(){
public Object doInTopLink(Session session) {
return session.executeQuery("findAllTeam", Team.class);
}
});
}
当然,您可以添加参数或使用在 TopLink 映射中定义的另一个命名查询。
由于您需要读取所有小组,因此您可以使用该模板的简便方法(参见清单 3)。
清单 3. 使用简便方法替换 Spring。
public List<team> findAllTeams() {
return getTopLinkTemplate().readAll(Team.class);
}
- 编辑 TopLinkCompetitionDao 类并实施 findAllTeams。
- 删除“return null”语句。
- 添加使用 TopLinkTemplate.readAll(…) 方法的代码。
- 运行 CompetitionDaoTopLinkTest 类并查看 testGetAllTeams 测试现在是否成功。
同一逻辑适用于 findAllClubs(参见清单 4):
清单 4. Spring 的 FindAllClubs()。
public List<Club> findAllClubs(){
return getTopLinkTemplate().readAll(Club.class);
}
接下来,
- 编辑 TopLinkCompetitionDao 类并实施 findAllClubs(参见清单 4)。
- 删除“return null”语句。
- 添加使用 TopLinkTemplate.readAll(…) 方法的代码。
- 运行 CompetitionDaoTopLinkTest 类并查看 testFindAllClubs 测试是否成功。
插入数据。由于用户可以向俱乐部添加小组,因此必须实施 saveTeam(Team)。如果不使用 Spring,您的代码将如清单 5 所示:
清单 5. 不使用 Spring 保存小组。
public void saveTeam(Team entity) {
UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
uow.registerNewObject(entity);
uow.commit();
}
正如您所见,获取资源和管理事务的过程嵌入在该代码中。
通过 Spring,使用 AOP 在应用程序上下文中声明事务。该方法现在如清单 6 所示:
清单 6. 使用 Spring 保存小组。
public void saveTeam(Team team) {
getTopLinkTemplate().merge(team);
}
- 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 merge(..) 方法实施 saveTeam 方法。
- 运行 CompetitionDaoTopLinkTest 类并查看 testSaveTeam 测试是否成功。
您可以使用若干变体:deepMerge、shallowMerge 和 mergeWithReferences。使用哪个取决于您希望相关对象进行何种操作。这些方法与 UnitOfWork API 上的相应方法等效。
更新数据。用户可通过添加或更改备注来更新小组。如果不使用 Spring,代码如清单 7 所示:
清单 7. 更新该小组的代码(不使用 Spring)。
public void updateTeam(Team entity) {
UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
Object workingCopy = uow.readObject(entity);
if (workingCopy == null){
throw new RuntimeException("Couldnt find team to update");
}
uow.shallowMergeClone(entity)
uow.commit();
}
如果使用 Spring,该实施如清单 8 所示。
清单 8. 使用 Spring 更新小组。
public void updateTeam(Team team) {
getTopLinkTemplate().shallowMerge(team);
}
重要的是,合并的实体在更改前要注册到会话,否则调用清单 8 中的方法将引发异常。这就是为什么当用户选择一个小组进行编辑时,通过调用 loadTeam(…) 读取它的原因,如清单 9 所示。
清单 9. 加载该小组以将它注册到会话。
public Team loadTeam(Long teamId) {
Team t = null;
try {
t = (Team)getTopLinkTemplate().readAndCopy(Team.class, teamId);
} catch (ObjectRetrievalFailureException e) {
if (logger.isDebugEnabled()) {
logger.debug("team with id: " + teamId + "is not found");
}
}
return t;
}
- 编辑 TopLinkCompetitionDao 类并实施 updateTeam 方法。使用 TopLinkTemplate 的 shallowMerge(..) 方法(参见清单 8)。
- 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 readAndCopy(..) 方法实施 loadTeam(参见清单 9)。
- 运行 CompetitionDaoTopLinkTest 类并查看 testUpdateTeam 和 testLoadTeam 测试是否成功。
删除数据。因为小组还可以删除,因此必须实施 deleteTeam(Team)。如果我们不使用 Spring,代码将如清单 10 所示。
清单 10. 不使用 Spring 删除小组。
public void deleteTeam(Team entity) {
UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
Object workingCopy = uow.readObject(entity);
if (workingCopy == null){
throw new RuntimeException("Could not find team to delete");
}
uow.deleteObject(workingCopy);
uow.commit();
}
在清单 11 中,您可以看到该代码类似于使用 TopLinkTemplate:
清单 11. 使用 TopLinkTemplate 删除该小组。
public void deleteTeam(Team team) {
getTopLinkTemplate().delete(team);
}
- 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 delete(..) 方法实施 deleteTeam 方法。
- 运行 CompetitionDaoTopLinkTest 类并查看 testDeleteTeam 方法是否成功。
由于所有测试成功,因此您实现了 TopLinkCompetitionDao 类。现在,我们详细看一下 CompetitionDaoTopLinkTest 类的实现。
配置资源和事务。当您将所有资源管理和事务管理代码从 DAO 类中删除之后,必须在 Spring 配置文件中配置它。这是在文件 ApplicationContext.xml 中进行的。
- 打开 applicationContext.xml。它位于 WEB-INF 下的 ViewController project 中(参见图 13)。
图 13. ViewController 项目中的 applicationContext。
在本例中,该容器处理事务和资源。您配置 Spring 上下文来使用 JtaTransactionManager。这意味着必须相应地配置 TopLink 会话(参见图 14);它需要指向一个数据源并使用内部事务控制器(参见 OTN 上以前发布的文章“什么是 Spring TopLink 集成?”)。
- 在 sessions.xml(在 Model 项目中)中查看 competitionSession 并确保它设置为使用外部事务控制器。
图 14. 使用 JTA 事务的设置。
- 通过添加以下清单 12 中显示的代码行,将 JtaTransactionManager 添加到 applicationContext.xml。
清单 12. 定义事务管理器的 applicationContext.xml 的片段。
<!-- JTA transaction manager for J2EE environments -->
<bean name="myTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"<//>
该数据源使用 JNDI 从容器获取。这是通过定义类 JndiObjectFactoryBean 的一个 bean 来完成的。
- 将数据源添加到 applicationContext.xml(参见清单 13):
清单 13. 查看使用 JNDI 名的数据源的代码。
<!-- JNDI DataSource for J2EE environments -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/competitionDS"<//>
</bean>
该数据源和会话都在 LocalSessionFactoryBean 中定义。
- 将 sessionFactory 添加到 applicationContext.xml:
清单 14. Sessionfactory 配置。
<!-- the sessionfactory -->
<bean name="sessionFactory"
class="org.springframework.orm.toplink.LocalSessionFactoryBean">
<property name="configLocation">
<value>sessions.xml</value>
</property>
<property name="sessionName">
<value>competitionSession</value>
</property>
<property name="dataSource" ref="dataSource"<//>
<property name="sessionLog">
<bean class=
"org.springframework.orm.toplink.support.CommonsLoggingSessionLog"<//>
</property>
</bean>
TopLinkCompetitionDao 中注入了该会话工厂。
- 通过添加清单 15 的代码定义 competionDao bean 并设置该会话工厂:
清单 15. DAO 类的配置。
<!--data access class with session factory -->
<bean name="competitionDao"
class="com.xebia.demo.hockey.dao.toplink.TopLinkCompetitionDao">
<property name="sessionFactory">
<ref bean="sessionFactory"<//>
</property>
</bean>
该 competition 服务也在该上下文中定义。您在清单 15 中定义的 competitionDao 使用设值方法注入 (setter injection) 注入到该服务中。
- 通过将以下定义添加到 applicationContext.xml 定义 competitionService:
清单 16. Ccompetition 服务的定义。
<!-- the service class that is injected when
the JSF pages call competitionService -->
<bean name="competitionServiceTarget"
class="com.xebia.demo.hockey.service.impl.CompetitionServiceImpl" >
<property name="competitionDao">
<ref bean="competitionDao"<//>
</property>
</bean>
使用 AOP 为 CompetitionService 类上的每个方法定义事务。您使用通配符“*”截取 CompetitionService 并针对每个方法使用一个新事务(参见清单 17)。
- 通过将以下代码添加到 applicationContext.xml 来添加截取程序:
清单 17. 使用 AOP 定义事务。
<!-- the bean that intercepts the competitionService to wrap a transaction
around the methods -->
<bean id="competitionService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="myTransactionManager"<//>
</property>
<property name="target">
<ref bean="competitionServiceTarget"<//>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
产生的配置文件应该如图 15 所示。
图 15. 文件 applicationContext.xml 中的结果配置
测试代码
现在应该检查测试代码了。图 16 显示了这些测试的类模型。对于 TopLinkCompetitionDao 类中的每个方法都有一个测试方法。这些测试针对 Oracle 数据库来运行。在本例中,我用 DbUnit 初始化 XML 文件的数据。JUnit 用于编写测试用例并声明结果。类 AbstractTransactionalDataSourceSpringContextTests 确保测试运行在事务中并在完成后回滚。在这些测试中,我用 jdbcTemplate 检索数据并计算行。
图 16. 测试代码的类模型。
对于这些测试,Spring 进行不同配置。AbstractDaoTest 定义了 getConfigLocations() 方法中 spring 配置的位置。它指向文件 test-config.xml。与 applicationContext 的最主要区别是:
- 它使用 org.springframework.jdbc.datasource.DriverManagerDataSource。
- 它使用 TopLinkTransactionManager 而非 JtaTransactionManager。
- 不定义事务边界,因为它们由 AbstractTransactionalDataSourceSpringContextTests 类处理。
使用该方法,该测试代码变得非常简单。清单 18 显示了 testGetAllTeams() 的示例:
清单 18. CompetitionDaoTopLinkTest 中的测试方法示例。
/**
* Tests the method that gets all teams that belong to a competition.
* Fields that are fetched include the club and the name
*/
public void testGetAllTeams(){
List<Team> teams = competitionDao.findAllTeams();
assertNotNull(teams);
assertEquals(2, teams.size());
Team teamOne = teams.get(0);
assertNotNull(teamOne.getClub());
assertEquals("Kampong", teamOne.getClub().getName());
assertNotNull(teamOne.getName());
}
AbstractXXXSpringContextTests 类中有几个简便方法可用。这些方法(用在若干测试中)在表 2 中有述。
表 2. 测试类中使用的方法。
|
方法名
|
说明
|
适用的方法
|
|
deleteFromTables(String[] tables)
|
从 String 数组的表中删除数据。
|
testFindAllClubs
|
|
setComplete()
|
防止事务回滚。
|
testSaveTeam
testDeleteTeam
|
|
endTransaction()
|
如果调用 setComplete(),该事务最终不回滚。
|
testSaveTeam
testDeleteTeam
|
|
startNewTransaction()
|
在方法中启动新事务。通过这种方式可能创建和结束任意数量的事务。当删除测试用例时,最后的事务将自动回滚。
|
testSaveTeam
testDeleteTeam
|
如果您用 testSaveTeam 或 testDeleteTeam 方法注释 setComplete() 和 endTransaction(),则运行该代码看看会发生什么情况。因为 jdbcTemplate 不使用 TopLink 会话而且该事务仍在运行,所以这些测试应该失败。
部署应用程序
测试了该应用程序后,就可以使用部署配置文件、ant 或 maven 对其进行部署了(参见图 17)。对于本例而言,使用部署配置文件。
- 编辑 webapp.deploy 配置文件。
图 17. 需要使用应用程序部署的库。
- 启动独立的 OC4J。确保它运行 JDK1.5。
- 在 JDeveloper 中使用 Connections 选项卡创建到 OC4J 的新连接。
- 使用部署配置文件将应用程序部署到独立的 OC4J。
- 测试应用程序。
- 打开一个 Web 浏览器。
- 键入 http://localhost:8888/competition/faces/teams.jsp。
- 编辑、添加和删除一些小组(参见图 18)。
图 18. 应用程序的启动页面
下一步要做什么呢?
到本文完稿时为止,Spring 框架支持 TopLink、Hibernate、JDO 和其他数据访问框架。Spring 2.0(将包括 Spring JPA)将针对 Java Persistence API 提供类似的广泛支持。它也将包括 TopLink Essentials(TopLink 的开放源代码版本)。
结论
在本文中,您已经学习了如何在 JDeveloper 中使用 Spring 和 TopLink。建立工作区是容易的,而且您会为能够以您习惯的方式设计和编码 TopLink 应用程序而感到满意。资源和事务管理可以在 Spring 配置中以声明方式进行定义,从而可以分别给予关注。除了使代码更易于维护和测试,这也使移植到 EJB 3.0 和 JPA 更为容易。
Lonneke Dikmans(OFM 区域总监)是荷兰 Xebia 软件开发的高级顾问。她是获得 Sun 认证的 Java 程序员(针对 Java 2 平台),还是 Oracle 融合中间件区域总监。Lonneke 自从 2000 年以来一直使用 JDeveloper,具有设计、开发和部署 J2EE 应用程序的经验。 |