开发人员:J2EE
   下载
 Oracle TopLink
 Oracle JDeveloper 10g (10.1.3)
 Spring 框架
 
   关键词
开放源代码java全部
 

 

结合使用 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):

  1. 将该 zip 文件提取到您选择的文件夹中。
  2. 单击绿色加号导入现有应用程序。
  3. 导航到您将 zip 文件提取到的文件夹。
  4. 双击 HockeyCoachAssistant.jws 文件。

图 1
图 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 项目的库:

  1. 添加 Spring 作为一个库。
    • 下载 Spring Framework 1.2.8
    • 用名称 Spring128 创建一个用户定义的库。
    • 添加类、源和文档位置

该 Model 项目现在如图 2 所示。

图 2
图 2. 编译和运行该 Model 项目所需的库。

接下来,将库添加到 TestModel 项目:

  1. 添加 spring128 库。
  2. 添加 spring-mock 库。
    • 使用名称 Spring128Test 创建用户定义的库。
    • 将 spring-mock-jar 添加到类中。
  3. 添加 JUnit 库。
    • 请使用帮助单中的选项“Check for Updates”来安装 JUnix 运行时。
    • 将 JUnit Runtime 库添加到该项目。
  4. 添加 DbUnit 库。
    • www.dbunit.org 下载 DbUnit。
    • 创建名为 dbunit 的用户定义库。
    • 将 dbunit2.1.jar 添加到该类路径。
    • 或者,下载这些源,然后也将它们添加到该库中。

TestModel 项目现在看起来如图 3 所示。

图 3
图 3. 编译和运行 TestModel 项目所需的库。

最后,将所需的库添加到 ViewController 项目:

  1. 添加您为该模型项目创建的 spring128 库。

ViewController 项目现在应该如图 4 所示:

图 4
图 4. 编译和运行 ViewController 项目所需的库。

示例应用程序

建立了工作区后,您就可以关注 HockeyCoachAssistant 应用程序了。这个简单的程序记录了一场曲棍球赛中的队伍;用户可以添加、删除或编辑比赛的球队。

该用户界面是一个 Web 应用程序,它包含带有 ADF Faces 组件的 java 服务器页面 (jsp)。启动页面 teams.jsp(参见图 5)。

图 5
图 5. 以 faces-config.xml 中定义的方式在应用程序中导航。

该应用程序包含 4 层:模型、数据访问 (DAO)、服务和视图(参见图 6)。本文关注 DAO 层,它是使用 Spring 对 TopLink 的支持的地方。

图 6
图 6. HockeyCoachAssistant 应用程序的逻辑视图。

安装应用程序

要显示 TopLink 支持如何用在 HockeyCoachAssistant 应用程序中,必须创建到该数据库的连接并创建模式和表。

  1. 通过在该 Model 项目的 Database 文件夹中运行 SQL 脚本创建该数据库模式。
    • 通过利用诸如 SQLPlus 或 SQLDeveloper 的工具运行文件 compUser.sql 来创建一个称为“comp”的用户。确保作为系统或有权创建用户的另一个用户进行连接。
    • 以用户“comp”的身份进行连接,并使用同一工具运行 createTable.sql 脚本。
  2. 在 JDeveloper 中使用 Connections 选项卡创建数据库连接。
    • 创建称为 Competition 的新数据库连接。
    • 输入用户名 (“comp”) 和密码 (“comp”)。
    • 输入 SID 指向您的数据库。
  3. 在 sessions.xml 中纠正连接设置(参见图 7)。
    • 在 Model 项目的 src\META-INF 文件夹中打开 sessions.xml。
    • 在 Structure 窗格中双击称为 localSession 的会话。
    • 单击 Login。
    • 更新 JDBC 连接 URL 以指向您的数据库。
    • 如果需要,更新数据库平台。

    图 7
    图 7. 纠正 localSession 的 URL。

  4. 纠正 competitionMap 的设置(参见图 7)。
    • 单击 TopLink 映射。
    • 编辑该连接 URL。
    • 如果需要,更改数据库平台。

    图 8
    图 8. 纠正 competitionMap。

  5. 在 TestModel 项目中编辑 test-toplink-context.xml 文件。
    • 双击该文件。
    • 更改数据库设置以匹配您的本地安装(参见图 9)。

    图 9
    图 9. 在 test-context.xml 中纠正 JDBC 连接的 URL。

  6. 检查该安装。
    • 重新构建工作区。
    • 生成 TopLink 映射状态报告。
    • 在 CompetitionDaoTopLinkTest 中运行测试并确保它们失败时都引发 org.springframework.beans.factory.BeanCreationException(参见图 10)。

    图 10
    图 10. 将出现错误,除非您实现 CompetitionDao 接口。

    现在您可以实施 CompetitionDao 类了。

数据访问层

数据访问的 Spring 支持包括两个部分:

  • 在 J2EE 应用程序中处理通用数据访问元素(包括事务管理、资源管理和异常处理)的 Template 类
  • 实施应用程序特定的细节的回调

这适用于 Spring 的所有数据访问支持,包括 TopLink、Hibernate、JDBC、IBATIS 和 JDO。使用 TopLinkTemplate 类有两种方式:您可以使用 Application Context 在应用程序中的每个 DAO 类中注入它,也可以扩展支持类。

我们的示例扩展了 TopLinkDaoSupport 类。这产生了以下数据访问组件结构(图 11):

图 11
图 11. 应用程序中的数据访问层结构。

TopLinkCompetitionDao(参见图 11)实施 CompetitionDao 接口,该接口向服务层公开。通过这种方式,数据访问实施细节就可以对应用程序的其他部分隐藏。

  1. 编辑 TopLinkCompetitionDao 类(参见图 12):
    • 允许类扩展 TopLinkDaoSupport
    • 导入 springframework.orm.toplink.support.TopLinkDaoSupport 类。

图 12
图 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);
}
  1. 编辑 TopLinkCompetitionDao 类并实施 findAllTeams。
    • 删除“return null”语句。
    • 添加使用 TopLinkTemplate.readAll(…) 方法的代码。
  2. 运行 CompetitionDaoTopLinkTest 类并查看 testGetAllTeams 测试现在是否成功。

同一逻辑适用于 findAllClubs(参见清单 4):

清单 4. Spring 的 FindAllClubs()。
public List<Club> findAllClubs(){
   return getTopLinkTemplate().readAll(Club.class);
}

接下来,

  1. 编辑 TopLinkCompetitionDao 类并实施 findAllClubs(参见清单 4)。
    • 删除“return null”语句。
    • 添加使用 TopLinkTemplate.readAll(…) 方法的代码。
  2. 运行 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);
 }

 

  1. 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 merge(..) 方法实施 saveTeam 方法。
  2. 运行 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;
}
  1. 编辑 TopLinkCompetitionDao 类并实施 updateTeam 方法。使用 TopLinkTemplate 的 shallowMerge(..) 方法(参见清单 8)。
  2. 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 readAndCopy(..) 方法实施 loadTeam(参见清单 9)。
  3. 运行 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);
}
  1. 编辑 TopLinkCompetitionDao 类并使用 TopLinkTemplate 的 delete(..) 方法实施 deleteTeam 方法。
  2. 运行 CompetitionDaoTopLinkTest 类并查看 testDeleteTeam 方法是否成功。

由于所有测试成功,因此您实现了 TopLinkCompetitionDao 类。现在,我们详细看一下 CompetitionDaoTopLinkTest 类的实现。

 

配置资源和事务。当您将所有资源管理和事务管理代码从 DAO 类中删除之后,必须在 Spring 配置文件中配置它。这是在文件 ApplicationContext.xml 中进行的。

  1. 打开 applicationContext.xml。它位于 WEB-INF 下的 ViewController project 中(参见图 13)。

    图 13
    图 13. ViewController 项目中的 applicationContext。

    在本例中,该容器处理事务和资源。您配置 Spring 上下文来使用 JtaTransactionManager。这意味着必须相应地配置 TopLink 会话(参见图 14);它需要指向一个数据源并使用内部事务控制器(参见 OTN 上以前发布的文章“什么是 Spring TopLink 集成?”)。

  2. 在 sessions.xml(在 Model 项目中)中查看 competitionSession 并确保它设置为使用外部事务控制器。

    图 14
    图 14. 使用 JTA 事务的设置。

  3. 通过添加以下清单 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 来完成的。

  4. 将数据源添加到 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 中定义。

  5. 将 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 中注入了该会话工厂。

  6. 通过添加清单 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) 注入到该服务中。

  7. 通过将以下定义添加到 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)。

  8. 通过将以下代码添加到 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
    图 15. 文件 applicationContext.xml 中的结果配置

测试代码

现在应该检查测试代码了。图 16 显示了这些测试的类模型。对于 TopLinkCompetitionDao 类中的每个方法都有一个测试方法。这些测试针对 Oracle 数据库来运行。在本例中,我用 DbUnit 初始化 XML 文件的数据。JUnit 用于编写测试用例并声明结果。类 AbstractTransactionalDataSourceSpringContextTests 确保测试运行在事务中并在完成后回滚。在这些测试中,我用 jdbcTemplate 检索数据并计算行。

图 16
图 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)。对于本例而言,使用部署配置文件。

  1. 编辑 webapp.deploy 配置文件。
    • 选择 spring128 库。

    图 17
    图 17. 需要使用应用程序部署的库。

  2. 启动独立的 OC4J。确保它运行 JDK1.5。
  3. 在 JDeveloper 中使用 Connections 选项卡创建到 OC4J 的新连接。
  4. 使用部署配置文件将应用程序部署到独立的 OC4J。
  5. 测试应用程序。
    • 打开一个 Web 浏览器。
    • 键入 http://localhost:8888/competition/faces/teams.jsp
    • 编辑、添加和删除一些小组(参见图 18)。

    图 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 应用程序的经验。