1. 可以在服务实现中利用 Spring DAO API。Spring DAO 实现了著名的 DAO 设计模式(请参阅 参考资料)。在这个模式中,DAO 提供了一致的数据访问外观。通过传输对象执行数据提取和修改。DAO 封装了实际的数据源,并提供了操作传输对象的方法。
从架构上说,DAO API 隐藏了操作实际数据持久性 API 调用的复杂性。(除了 JPA 之外,Spring 还支持其他 ORM 技术,例如 JDO、Hibernate、iBATIS SQL Maps 和 Apache OJB。)。使用 Spring 的 DAO,可以编写能够轻松适应这些持久性 API 的数据访问代码。
除了对数据持久性 API 的抽象,Spring 的 DAO 支持把各种特定于厂商的数据访问异常映射到一套归档良好的 Spring 数据访问异常。
Spring DAO API 还提供了便于扩展的支持类。通过扩展它们,您可不必再编写烦琐而易于出错的 ORM 数据访问代码。所需的全部编码都封装在基类和支持库中,而且经过全面测试。这些类封装了通常与应用程序逻辑混杂在一起的连接和事务管理代码。在 JPA 支持类的情况下,对 JPA 实体管理器的使用完全封装在支持类中,因此您不必考虑实体管理器和实体管理器工厂的处理。
一些真实的代码可以为您展现 Spring DAO API 的多功能性。清单 10 是 EmployeeService 接口的实现,称为 EmployeeDAO,它使用了 Spring 2 的 JpaDaoSupport 类:
import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
public class EmployeeDAO extends JpaDaoSupport implements EmployeeService {
public Employee findById(long id) {
return getJpaTemplate()。find(Employee.class, id);
}
public List<Employee> findAll() {
return getJpaTemplate()。find("select e from Employee e");
}
public List<Employee> findByEmployeeNumber(String empno) {
return getJpaTemplate()。find(
"select e from Employee e where e.empno = ?1", empno);
}
public List<Employee> findByAddressStreetName(String street) {
return getJpaTemplate()。find(
"select e from Employee e where e.addr.street = ?1", street);
}
public List<Employee> findByEmployeeLastName(String lastName) {
return getJpaTemplate()。find(
"select e from Employee e where e.lastName = ?1", lastName);
}
public List<Employee> findEmployeeWithSalaryOver(double sal) {
return getJpaTemplate()。find(
"select e from Employee e where e.salary > ?1", sal);
}
public List<Employee> findEmployeeWithCommissionOver(double comm) {
return getJpaTemplate()。find(
"select e from Employee e where e.commission > ?1", comm);
}
public Employee save(Employee emp) {
getJpaTemplate()。persist(emp);
return emp;
}
public Employee update(Employee emp) {
return getJpaTemplate()。merge(emp);
}
public void delete(Employee emp) {
getJpaTemplate()。remove(emp);
}
}
JpaDaoSupport 类处理了大多数烦琐的日常工作。JpaTemplate 助手类能:
隐藏底层的 API 差异
转换异常
管理 JPA 实体管理器
打包事务处理
把数据访问标准化成对少数一致(跨全部 Spring DAO 实现)和定义良好的方法的访问
在幕后,JpaTemplate 辅助类利用 JPA 实体管理器处理全部操作。辅助类处理数据访问期间例行的实体管理器检索和关闭操作。
在处理某些具体需求时,JpaTemplate 类中的其他方法可能有所帮助。请参考 Spring DAO API 的 JavaDoc 获得更多细节(请参阅 参考资料)。
有了保持 Employee 和 Address 实例的能力和 EmployeeService 的具体实现,接下来就可以根据真实关系数据库进行完整的测试了。
2. 连接 Spring bean
至此,对于 Spring 框架什么时候和如何获得机会去实际处理 POJO,仍然不清楚。谜题的数据访问部分解决了,但仍有两个问题存在:Spring 2 引擎怎么知道要做什么,如何指定要使用哪个关系数据库?
立即就会解决这两个问题;将看到如何向 Spring 引擎提供 bean 连接模板。秘密在于叫作 dwspring-service.xml 的 XML bean 描述符文件。这个 bean 描述符文件是 Spring 2 框架操作概述 中提过的连接蓝本。它描述了 Spring 应用程序中不同的 bean 之间的关系。
清单 11 显示了这个文件:
清单 11. dwspring-service.xml bean 描述符
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:dwspring"/>
<property name="username" value="sa" />
<property name="password" value=" " />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
用 Spring DAO 实现域服务
在这一节,您将用 Spring DAO(数据访问对象)API 实现员工信息应用程序的服务接口。
实现 EmployeeService 接口
一旦 Spring 2 引擎知道了如何保持 Employee 和 Address 对象的实例,实现 EmployeeService 接口的任务就变得非常简单。
可以在服务实现中利用 Spring DAO API。Spring DAO 实现了著名的 DAO 设计模式(请参阅 参考资料)。在这个模式中,DAO 提供了一致的数据访问外观。通过传输对象执行数据提取和修改。DAO 封装了实际的数据源,并提供了操作传输对象的方法。
从架构上说,DAO API 隐藏了操作实际数据持久性 API 调用的复杂性。(除了 JPA 之外,Spring 还支持其他 ORM 技术,例如 JDO、Hibernate、iBATIS SQL Maps 和 Apache OJB。)。使用 Spring 的 DAO,可以编写能够轻松适应这些持久性 API 的数据访问代码。
除了对数据持久性 API 的抽象,Spring 的 DAO 支持把各种特定于厂商的数据访问异常映射到一套归档良好的 Spring 数据访问异常。
Spring DAO API 还提供了便于扩展的支持类。通过扩展它们,您可不必再编写烦琐而易于出错的 ORM 数据访问代码。所需的全部编码都封装在基类和支持库中,而且经过全面测试。这些类封装了通常与应用程序逻辑混杂在一起的连接和事务管理代码。在 JPA 支持类的情况下,对 JPA 实体管理器的使用完全封装在支持类中,因此您不必考虑实体管理器和实体管理器工厂的处理。
一些真实的代码可以为您展现 Spring DAO API 的多功能性。清单 10 是 EmployeeService 接口的实现,称为 EmployeeDAO,它使用了 Spring 2 的 JpaDaoSupport 类:
清单 10. 用 Spring 2 的 JPA 支持实现的 EmployeeService 接口
import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
public class EmployeeDAO extends JpaDaoSupport implements EmployeeService {
public Employee findById(long id) {
return getJpaTemplate()。find(Employee.class, id);
}
public List<Employee> findAll() {
return getJpaTemplate()。find("select e from Employee e");
}
public List<Employee> findByEmployeeNumber(String empno) {
return getJpaTemplate()。find(
"select e from Employee e where e.empno = ?1", empno);
}
public List<Employee> findByAddressStreetName(String street) {
return getJpaTemplate()。find(
"select e from Employee e where e.addr.street = ?1", street);
}
public List<Employee> findByEmployeeLastName(String lastName) {
return getJpaTemplate()。find(
"select e from Employee e where e.lastName = ?1", lastName);
}
public List<Employee> findEmployeeWithSalaryOver(double sal) {
return getJpaTemplate()。find(
"select e from Employee e where e.salary > ?1", sal);
}
public List<Employee> findEmployeeWithCommissionOver(double comm) {
return getJpaTemplate()。find(
"select e from Employee e where e.commission > ?1", comm);
}
public Employee save(Employee emp) {
getJpaTemplate()。persist(emp);
return emp;
}
public Employee update(Employee emp) {
return getJpaTemplate()。merge(emp);
}
public void delete(Employee emp) {
getJpaTemplate()。remove(emp);
}
}
在清单 10 中,最值得注意的就是各方法实现中编码极其简单。JpaDaoSupport 类处理了大多数烦琐的日常工作。JpaTemplate 助手类能:
隐藏底层的 API 差异
转换异常
管理 JPA 实体管理器
打包事务处理
把数据访问标准化成对少数一致(跨全部 Spring DAO 实现)和定义良好的方法的访问
表 2 总结了清单 10 中的 JpaTemplate 方法,这是一种常用的方法:
表 2. EmployeeDAO 中的 JpaTemplate 方法
方法 说明
find(Class <T> cls, Object id); 通过实例的主键找到保持的实例。
find(String query); 使用查询字符串找到保持的对象。这个强大的查询语言是 EJB QL 的扩展版本,在 JSR-220 中有完整描述(请参阅 参考资料)。
persist(Object obj); 保存实例到数据库。用 JPA 的说法,它用 JPA 实体管理器保持实例。
merge(Object obj); 用所提供的实例中的信息更新保存的对象实例。
remove(Object obj); 从数据库中删除保持的实例。
在幕后,JpaTemplate 辅助类利用 JPA 实体管理器处理全部操作。辅助类处理数据访问期间例行的实体管理器检索和关闭操作。
在处理某些具体需求时,JpaTemplate 类中的其他方法可能有所帮助。请参考 Spring DAO API 的 JavaDoc 获得更多细节(请参阅 参考资料)。
有了保持 Employee 和 Address 实例的能力和 EmployeeService 的具体实现,接下来就可以根据真实关系数据库进行完整的测试了。
连接 Spring bean
至此,对于 Spring 框架什么时候和如何获得机会去实际处理 POJO,仍然不清楚。谜题的数据访问部分解决了,但仍有两个问题存在:Spring 2 引擎怎么知道要做什么,如何指定要使用哪个关系数据库?
立即就会解决这两个问题;将看到如何向 Spring 引擎提供 bean 连接模板。秘密在于叫作 dwspring-service.xml 的 XML bean 描述符文件。这个 bean 描述符文件是 Spring 2 框架操作概述 中提过的连接蓝本。它描述了 Spring 应用程序中不同的 bean 之间的关系。清单 11 显示了这个文件:
清单 11. dwspring-service.xml bean 描述符
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:dwspring"/>
<property name="username" value="sa" />
<property name="password" value=" " />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
要测试 EmployeeDAO 实现的动作,可以使用叫作 HSQLDB 的内存内(请参阅 参考资料)。HSQLDB 的可执行文件是 “有依赖项的 Spring 2” 下载的一部分。
在清单 11 中,专门配置 HSQLDB 实例的行用黑体表示。稍后(在 编写针对 RDBMS 的 DAO 集成测试)中,将看到如何修改这些行,来针对 DB2 Express-C 运行集成测试。
请记住 EmployeeDAO 实际上扩展了 JpaDaoSupport 类。这个类希望在装入的时候被 JPA EntityManagerFactory “插入”。然后它可以用这个工厂得到所有数据访问操作的 JPA EntityManager。
图 6 以图形方式显示 bean 在 dwspring2-service.xml 文件中如何连接在一起:
图 6. bean 连接示意图
实际上,清单 11 是需要由 Spring 2 引擎创建的对象的连接计划,图 6 是这些对象的图表。在清单 11 和图 6 中要注意的一个重要项目就是 EmployeeDAO 如何通过叫作 employeeService 的bean 获得。这个实例把自己的 entityManagerFactory 属性设置成另一个名为 entityManagerFactory的 bean:
[img]http://dl.iteye.com/upload/attachment/259545/a8c87a62-2556-3a79-b4d9-7bc44ed431c7.gif[/img]
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
ref="" 标志是对上下文中定义的另一个 bean 的引用 —— 通常是在同一个文件中。
3. 依赖性注入
用外部创建的对象来填充属性,就像刚才做的那样,这叫作 注入——更具体地讲,叫作依赖性注入 (DI),因为被注入的对象通常是接收对象进行正确操作所依赖的事物。DI 在 Spring 中框架中使用得很多。使用 DI,编写组件代码时不需要主动查询或查找依赖服务(例如,查询 EntityManagerFactory)。相反,可以就像依赖服务已经存在一样地编写组件代码,Spring 引擎会在代码执行之前把实际的依赖性注入组件。
依赖性注入的应用程序
如果查看 清单 11 中一直到 entityManagerFactory 连接的部分,您会注意到 Spring 注入了以下依赖项:
dataSource
jpaVendorAdapter
loadTimeWeaver
dataSource bean 是 org.springframework.jdbc.datasource.DriverManagerDataSource 的实例,用 HSQLDB RDBMS 的内存中实例进行了配置。
jpaVendorAdapter 属性通过连接到 Spring 应用程序实际 JPA 实现的 bean 注入。在这个示例中,使用的是 JPA 引用实现,通过 org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter 类来访问。这个类接着需要用 databasePlatform property 进行配置。这个属性被设置成 oracle.toplink.essentials.platform.database.HSQLPlatform,此配置支持对 HSQLDB RDBMS 的访问。这个 bean 的 generateDdl 属性控制着是否生成和执行数据定义语言脚本。如果这个属性设为 true,那么这个 bean 每次装入时,都会重新建立数据库中的方案。为了集成测试的目的,应当保持这个属性为 true。
在 dataSource bean 的配置中,创建了 org.springframework.jdbc.datasource.DriverManagerDataSource 的实例。它的参数设置有:
HSQLDB 数据库驱动程序
创建内存中数据库的 JDBC UR(JDBC URL 的 mem 部分)
用户名和口令(对 HSQLDB,默认分别是 sa 和 "")
最后一个 transactionManager bean 是为后面的集成测试配置的。不需要连接这个 bean,因为后面要用的测试基类会按类型查找这个 bean。
至此,您应已对 Spring 2 如何连接 bean 有了一定的连接。您还应了解如何把数据库从 HSQLDB 转换到 DB2 Express-C,这一步要在下一节进行)
4. 现在还剩下的唯一问题是:如何和在什么时候调用 Spring 2 引擎,它怎么知道该如何使用 dwspring2-service.xml 配置文件?
看一下 EmployeeServiceIntegrationTest.java 中的集成测试源代码,答案就一目了然了。这个集成测试针对实际的 RDBMS 测试 EmployeeService 的 EmployeeDAO 实现。请参阅清单 12 中的代码片断:
清单 12. EmployeeServiceIntegrationTest 中的集成测试(第 1 部分)
package com.test.dw.spring2;
import java.util.Date;
...
import org.springframework.test.jpa.AbstractJpaTests;
public class EmployeeServiceIntegrationTest extends AbstractJpaTests {
private EmployeeService employeeService;
private long JoeSmithId = 99999;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
protected String[] getConfigLocations() {
return new String[] { "classpath:/com/ibm/dw/spring2/dwspring2-service.xml" };
}
这套集成测试是在 Spring 2 库的 AbstractJpaTests 类的帮助下编写的。通过实现清单 12 中强调的 getConfigLocations() 方法,可以提供一个或多个需要由 Spring 2 引擎解析的 bean 配置文件。可以有多个配置文件,因为分离后端和用户界面 bean 配置文件是一种常见实践。
清单 12 中的 setEmployeeService() 是依赖性注入的示例。当 Spring 2 引擎装入这个 EmployeeServiceIntegrationTest 类时(派生自 AbstractJpaTests),它发现一个没有填充的依赖项 —— 类型为 EmployeeService 的属性。引擎就查找 dwspring2-service.xml 文件,查找配置好的类型为 EmployeeService 的bean,并通过 setEmployeeService() 方法注入。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
按类型自动连接
您可能注意到,在 dwspring2-service.xml 文件中,对于 employeeService 的注入缺少明确的注入指令。实际上,这个注入是自动发生的。在 Spring 的术语中,这叫作自动连接。
AbstractJpaTests 基类派生自 AbstractDependencyInjectionSpringContextTests 类。 AbstractDependencyInjectionSpringContextTests 通过把 Spring 的按类型自动连接特性设为默认,简化了测试。如果在应用程序的上下文(在这个示例中,由 dwspring2-service.xml 文件配置)中发现相同类型的 bean,那么它的子类(EmployeeServiceIntegrationTest 就是这样一个子类)的任何依赖项(公共属性)就被自动注入。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
集成测试设置
AbstractJpaTests 针对测试的一个有用特性,就是在事务中执行测试,然后在测试后回滚所有效果的能力。这切实地加快了测试,因为在每次测试运行期间,不再需要删除和重建数据了。
清单 13 显示了执行每个测试的初始设计的代码。这个设置代码用三个 Employee 和它们的相关 Addresse 填充数据库。必须在与每个测试相同的事务内执行这个代码。否则,就不会看到注入的数据,因为事务总会被回滚。要在相同事务中执行设置,要覆盖 onSetUpInTransaction() 方法,如清单 13 所示:
清单 13. EmployeeServiceIntegrationTest 中的集成测试(第 2 部分)
protected void onSetUpInTransaction() throws Exception {
Employee emp1 = new Employee("0001", "Joe", "R","Smith",
"4853", "Engineer", 3, 'M',
20000.00, 0.00, 0.00,
new Address(10, "Walker Street")
, new Date(), new Date());
Employee emp2 = new Employee("0002", "John","T","Lockheed",
"4333", "Sales", 2, 'M',
40000.00, 0.00, 5000.00,
new Address(20, "Walker Street")
, new Date(), new Date());
Employee emp3 = new Employee("0003", "Mary","M","Johnson",
"4383", "Admin", 3, 'F',
60000.00, 0.00, 390.00,
new Address(123, "Booth Ave")
, new Date(), new Date());
employeeService.save(emp1);
employeeService.save(emp2);
employeeService.save(emp3);
JoeSmithId = emp1.getEmpid();
}
请注意,通过基于 JPA 的 employeeService 创建并保持 Employee 实例有多简单。因为 save() 方法调用 JPA 的 persist() 操作,而且操作会从 Employee 级联到 Address 对象(在 Employee POJO 的 JPA 注释指定这个),所以也可以依靠 JPA 在地址表中创建新记录。
各测试每次执行时,都会执行 onSetUpInTransaction() 中的设置代码。这确保了在每次测试之前,都有三个员工。
作为测试示例,清单 14 显示了 EmployeeDAO 的 update() 方法的测试方法 testModifyEmployee():
清单 14. EmployeeServiceIntegrationTest 的集成测试(第 3 部分)
public void testModifyEmployee() {
String oldLastName = "Lockheed";
String newLastName = "Williams";
Employee emp = employeeService
.findByEmployeeLastName(oldLastName)。get(0);
emp.setLastName(newLastName);
Employee emp2 = employeeService.update(emp);
assertEquals(newLastName, emp2.getLastName());
List<Employee> results = employeeService
.findByEmployeeLastName(oldLastName);
assertEquals(0, results.size());
results = results = employeeService
.findByEmployeeLastName(newLastName);
assertEquals(1, results.size());
}
清单 14 中的测试用例 testModifyEmployee() 用 EmployeeService 的 update()把员工“John Lockheed”的姓氏从“Lockheed”改成“Williams”。然后它调用 findByEmployeeLastName("Williams"),检验最后有一个员工“Williams”。它还检验没有姓氏为“Lockheed”的员工存在。
EmployeeServiceIntegrationTest 中还有其他许多测试。可以研究这些代码查看如何利用 EmployeeService 实现上的方法操纵实际数据(请参阅 下载)。
接下来,设置根据 RDBMS 运行这些集成测试的环境。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
包含 persistence.xml 文件
虽然在这个 Spring JPA 集成场景中, persistence.xml 文件没有提供配置信息,但 JPA 规范要求这个文件。
persistence.xml 文件是持久性单元 的说明。在 JPA 术语中,持久性单元包含 EntityManagerFactory (和相关的配置信息),它创建的 EntityManagers 和这些 EntityManager 管理的类(以及这些类的元数据,以注释或 XML 形式)。
在这个示例中,persistence.xml 文件非常简单,如清单 15 所示:
清单 15. META-INF/persistence.xml 文件
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="dwSpring2Jpa" transaction-type="RESOURCE_LOCAL"/>
</persistence>
在某些厂商的配置中,persistence.xml 文件可以包含相关的说明信息,但是 Spring/JPA 集成中没有。只需要确定具有一份 META-INF/persistence.xml 的副本即可。
[b]下面顺便说下在Jboss环境中操作JPA。[/b]
1. persistence.xml 文件需包含比较多的内容:
<?xml version="1.0" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="slam-repository">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/sla_repository</jta-data-source>
<!-- 所以得PO 类 -->
<class>com.test.PO.xx.java</class>
。。。
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.jdbc.fetch_size" value="100" />
<property name="hibernate.jdbc.batch_size" value="50" />
<property name="jboss.entity.manager.factory.jndi.name"
value="java:/testEntityManagerFactory" />
</properties>
</persistence-unit>
</persistence>
2. 代码中获得 EM
try {
InitialContext ic = new InitialContext();
xts = (TransactionManager) ic.lookup("java:/TransactionManager");
this.entityManagerFactory = (EntityManagerFactory) ic
.lookup("java:/slarepositoryEntityManagerFactory");
// entityManager = entityManagerFactory.createEntityManager();
} catch (Exception e) {
e.printStackTrace();
}
。。。
xts.begin();
EntityManager saveManager = this.saveManagerFactory
.createEntityManager();
saveManager.joinTransaction();
...
为 Spring 2 准备 Tomcat
在可以向 Tomcat 成功部署 dwspring.war 文件之前,需要进行一些服务器设置。
在这一节执行的主要过程有:
把 Spring 2 类装入器添加到 Tomcat
把 Spring 2 上下文装入器侦听器添加到 Tomcat
把 DB2 JDBC 驱动程序复制到 Tomcat
为 Tomcat 配置 JNDI DB2 数据源
把 Spring 2 类装入器添加到 Tomcat 服务器
当 Spring JPA 应用程序在 Tomcat 上运行时,要让 JPA 支持正常工作,需要在类装入期间进行字节码“连接”。来自 Tomcat 的标准类装入器不支持这个。需要用特定于 Spring 的类装入器实现这个功能。
要把这个特定于 Spring 的类装入器安装到 Tomcat 服务器,首先要把 spring-tomcat-weaver.jar 拷贝到 Tomcat 的 server/lib 子目录。这个目录包含的库属于 Tomcat 服务器私有。可以在 Spring 2.0 下载的 dist/weaver 目录下找到 spring-tomcat-weaver.jar 库。
接下来,必须让 Tomcat 知道对于示例应用程序,应当替换标准类装入器。可以在 WAR 文件的 META-INF/context.xml 文件中指定这点。清单 25 中的粗体代码配置类装入器:
清单 25. 在 META-INF/context.xml 文件中配置类装入器
<Context>
<Loader loaderClass="org.springframework.instrument.
classloading.tomcat.TomcatInstrumentableClassLoader"/>
...
</Context>
把 Spring 2 的上下文装入器侦听器添加到 Tomcat
Spring 2 要求挂接到 Tomcat 的上下文装入管道。可以在 WAR 文件的 WEB-INF/web.xml 文件添加以下行进行这个配置:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
在 web.xml 文件中,这必须在 <servlet> 和 <servlet-mapping> 定义之前。
在 Tomcat 5 上配置 DB2 数据源管理和 JNDI
可以把一个应用程序可以访问的 DB2 数据源配置成 Web 应用程序上下文的 JNDI 资源。方法是把清单 26 中突出的代码放在自己 WAR 的 META-INF/context.xml 文件中:
清单 26. 在 META-INF/context.xml 中配置 JNDI 资源
<Context>
...
<Resource name="jdbc/dwspring2" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="bill"
password="lotus123" driverClassName="com.ibm.db2.jcc.DB2Driver"
url="jdbc:db2://192.168.23.36:50000/dwspring"/>
</Context>
需要替换清单 26 中的 DB2 Express-C 服务器主机、用户名和口令,反映自己的 DB2 Express-C 安装情况。
清单 27 的配置通过名称 java:comp/env/jdbc/dwspring2 提供了 JNDI 数据源。
还必须向部署描述符 web.xml 文件添加资源引用。请把这个 <resource-ref> 元素添加到 web.xml 文件的末尾,如清单 27 所示:
清单 27. 添加数据源的 JNDI 资源引用
...
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/dwspring2</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
清单 27 的配置使得可以在 Web 应用程序中使用容器 JDBC 管理的数据源。
为 Tomcat 部署配置 Spring 2 应用程序
要为 Tomcat 部署配置数据层代码,仍然需要两个细节:
告诉 Spring 2 引擎关于 bean 描述符配置文件的位置
把 Spring 2 数据源配置的连接改为通过 JNDI 使用 Tomcat 的数据源管理和池管理
告诉 Spring 2 引擎配置文件的位置
要让 Spring 2 引擎连接需要的数据层 bean,首先必须中找到并处理数据层的 bean 描述符配置文件。
在这个示例中,文件叫作 dwspring-service.xml。需要在部署的时候,在提供给 Tomcat 服务器的上下文参数中指定这个文件的位置。
这个上下文参数需要是 WEB-INF/web.xml 部署描述符的第一个元素,在清单 28 中用粗体表示:
清单 28. web.xml 中的上下文参数
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
spring2web</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dwspring2-service.xml</param-value>
</context-param>
...
dwspring2-service.xml 是前面在测试时使用的配置文件的改动版本。
这个配置文件被修改成通过 JNDI 使用 Tomcat 服务器的 JDBC 连接管理,而不是自己的连接管理。
修改 Spring 2 连接查找 Tomcat JNDI 数据源
dwspring2-service.xml 中使用 Tomcat 5 得到 JNDI 数据源需要进行的修改在清单 29 中已突出显示:
清单 29. 修改 dwspring2-service.xml 以进行 Tomcat JNDI 查询
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="databasePlatform" value=
"oracle.toplink.essentials.platform.database.DB2Platform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/dwspring2" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
清单 29 中用于创建数据源的 bean 现在是 org.springframework.jndi.JndiObjectFactoryBean。可以用这个 bean 执行对特定的容器管理资源的 JNDI 查询。在这个示例中,Tomcat 管理的 DB2 Express-C 数据源的名称被配置成 java:comp/env/jdbc/dwspring2。
在清单 29 中,请注意 jpaVendorAdapter 的 GenerateDdl 属性被设置成 false。这是必需的,因为不想让 Spring 2 在每次启动应用程序时都删除和重建所有数据库表。只有在集成测试期间,这个属性才应设置成 true。
从架构上说,DAO API 隐藏了操作实际数据持久性 API 调用的复杂性。(除了 JPA 之外,Spring 还支持其他 ORM 技术,例如 JDO、Hibernate、iBATIS SQL Maps 和 Apache OJB。)。使用 Spring 的 DAO,可以编写能够轻松适应这些持久性 API 的数据访问代码。
除了对数据持久性 API 的抽象,Spring 的 DAO 支持把各种特定于厂商的数据访问异常映射到一套归档良好的 Spring 数据访问异常。
Spring DAO API 还提供了便于扩展的支持类。通过扩展它们,您可不必再编写烦琐而易于出错的 ORM 数据访问代码。所需的全部编码都封装在基类和支持库中,而且经过全面测试。这些类封装了通常与应用程序逻辑混杂在一起的连接和事务管理代码。在 JPA 支持类的情况下,对 JPA 实体管理器的使用完全封装在支持类中,因此您不必考虑实体管理器和实体管理器工厂的处理。
一些真实的代码可以为您展现 Spring DAO API 的多功能性。清单 10 是 EmployeeService 接口的实现,称为 EmployeeDAO,它使用了 Spring 2 的 JpaDaoSupport 类:
import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
public class EmployeeDAO extends JpaDaoSupport implements EmployeeService {
public Employee findById(long id) {
return getJpaTemplate()。find(Employee.class, id);
}
public List<Employee> findAll() {
return getJpaTemplate()。find("select e from Employee e");
}
public List<Employee> findByEmployeeNumber(String empno) {
return getJpaTemplate()。find(
"select e from Employee e where e.empno = ?1", empno);
}
public List<Employee> findByAddressStreetName(String street) {
return getJpaTemplate()。find(
"select e from Employee e where e.addr.street = ?1", street);
}
public List<Employee> findByEmployeeLastName(String lastName) {
return getJpaTemplate()。find(
"select e from Employee e where e.lastName = ?1", lastName);
}
public List<Employee> findEmployeeWithSalaryOver(double sal) {
return getJpaTemplate()。find(
"select e from Employee e where e.salary > ?1", sal);
}
public List<Employee> findEmployeeWithCommissionOver(double comm) {
return getJpaTemplate()。find(
"select e from Employee e where e.commission > ?1", comm);
}
public Employee save(Employee emp) {
getJpaTemplate()。persist(emp);
return emp;
}
public Employee update(Employee emp) {
return getJpaTemplate()。merge(emp);
}
public void delete(Employee emp) {
getJpaTemplate()。remove(emp);
}
}
JpaDaoSupport 类处理了大多数烦琐的日常工作。JpaTemplate 助手类能:
隐藏底层的 API 差异
转换异常
管理 JPA 实体管理器
打包事务处理
把数据访问标准化成对少数一致(跨全部 Spring DAO 实现)和定义良好的方法的访问
在幕后,JpaTemplate 辅助类利用 JPA 实体管理器处理全部操作。辅助类处理数据访问期间例行的实体管理器检索和关闭操作。
在处理某些具体需求时,JpaTemplate 类中的其他方法可能有所帮助。请参考 Spring DAO API 的 JavaDoc 获得更多细节(请参阅 参考资料)。
有了保持 Employee 和 Address 实例的能力和 EmployeeService 的具体实现,接下来就可以根据真实关系数据库进行完整的测试了。
2. 连接 Spring bean
至此,对于 Spring 框架什么时候和如何获得机会去实际处理 POJO,仍然不清楚。谜题的数据访问部分解决了,但仍有两个问题存在:Spring 2 引擎怎么知道要做什么,如何指定要使用哪个关系数据库?
立即就会解决这两个问题;将看到如何向 Spring 引擎提供 bean 连接模板。秘密在于叫作 dwspring-service.xml 的 XML bean 描述符文件。这个 bean 描述符文件是 Spring 2 框架操作概述 中提过的连接蓝本。它描述了 Spring 应用程序中不同的 bean 之间的关系。
清单 11 显示了这个文件:
清单 11. dwspring-service.xml bean 描述符
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:dwspring"/>
<property name="username" value="sa" />
<property name="password" value=" " />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
用 Spring DAO 实现域服务
在这一节,您将用 Spring DAO(数据访问对象)API 实现员工信息应用程序的服务接口。
实现 EmployeeService 接口
一旦 Spring 2 引擎知道了如何保持 Employee 和 Address 对象的实例,实现 EmployeeService 接口的任务就变得非常简单。
可以在服务实现中利用 Spring DAO API。Spring DAO 实现了著名的 DAO 设计模式(请参阅 参考资料)。在这个模式中,DAO 提供了一致的数据访问外观。通过传输对象执行数据提取和修改。DAO 封装了实际的数据源,并提供了操作传输对象的方法。
从架构上说,DAO API 隐藏了操作实际数据持久性 API 调用的复杂性。(除了 JPA 之外,Spring 还支持其他 ORM 技术,例如 JDO、Hibernate、iBATIS SQL Maps 和 Apache OJB。)。使用 Spring 的 DAO,可以编写能够轻松适应这些持久性 API 的数据访问代码。
除了对数据持久性 API 的抽象,Spring 的 DAO 支持把各种特定于厂商的数据访问异常映射到一套归档良好的 Spring 数据访问异常。
Spring DAO API 还提供了便于扩展的支持类。通过扩展它们,您可不必再编写烦琐而易于出错的 ORM 数据访问代码。所需的全部编码都封装在基类和支持库中,而且经过全面测试。这些类封装了通常与应用程序逻辑混杂在一起的连接和事务管理代码。在 JPA 支持类的情况下,对 JPA 实体管理器的使用完全封装在支持类中,因此您不必考虑实体管理器和实体管理器工厂的处理。
一些真实的代码可以为您展现 Spring DAO API 的多功能性。清单 10 是 EmployeeService 接口的实现,称为 EmployeeDAO,它使用了 Spring 2 的 JpaDaoSupport 类:
清单 10. 用 Spring 2 的 JPA 支持实现的 EmployeeService 接口
import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
public class EmployeeDAO extends JpaDaoSupport implements EmployeeService {
public Employee findById(long id) {
return getJpaTemplate()。find(Employee.class, id);
}
public List<Employee> findAll() {
return getJpaTemplate()。find("select e from Employee e");
}
public List<Employee> findByEmployeeNumber(String empno) {
return getJpaTemplate()。find(
"select e from Employee e where e.empno = ?1", empno);
}
public List<Employee> findByAddressStreetName(String street) {
return getJpaTemplate()。find(
"select e from Employee e where e.addr.street = ?1", street);
}
public List<Employee> findByEmployeeLastName(String lastName) {
return getJpaTemplate()。find(
"select e from Employee e where e.lastName = ?1", lastName);
}
public List<Employee> findEmployeeWithSalaryOver(double sal) {
return getJpaTemplate()。find(
"select e from Employee e where e.salary > ?1", sal);
}
public List<Employee> findEmployeeWithCommissionOver(double comm) {
return getJpaTemplate()。find(
"select e from Employee e where e.commission > ?1", comm);
}
public Employee save(Employee emp) {
getJpaTemplate()。persist(emp);
return emp;
}
public Employee update(Employee emp) {
return getJpaTemplate()。merge(emp);
}
public void delete(Employee emp) {
getJpaTemplate()。remove(emp);
}
}
在清单 10 中,最值得注意的就是各方法实现中编码极其简单。JpaDaoSupport 类处理了大多数烦琐的日常工作。JpaTemplate 助手类能:
隐藏底层的 API 差异
转换异常
管理 JPA 实体管理器
打包事务处理
把数据访问标准化成对少数一致(跨全部 Spring DAO 实现)和定义良好的方法的访问
表 2 总结了清单 10 中的 JpaTemplate 方法,这是一种常用的方法:
表 2. EmployeeDAO 中的 JpaTemplate 方法
方法 说明
find(Class <T> cls, Object id); 通过实例的主键找到保持的实例。
find(String query); 使用查询字符串找到保持的对象。这个强大的查询语言是 EJB QL 的扩展版本,在 JSR-220 中有完整描述(请参阅 参考资料)。
persist(Object obj); 保存实例到数据库。用 JPA 的说法,它用 JPA 实体管理器保持实例。
merge(Object obj); 用所提供的实例中的信息更新保存的对象实例。
remove(Object obj); 从数据库中删除保持的实例。
在幕后,JpaTemplate 辅助类利用 JPA 实体管理器处理全部操作。辅助类处理数据访问期间例行的实体管理器检索和关闭操作。
在处理某些具体需求时,JpaTemplate 类中的其他方法可能有所帮助。请参考 Spring DAO API 的 JavaDoc 获得更多细节(请参阅 参考资料)。
有了保持 Employee 和 Address 实例的能力和 EmployeeService 的具体实现,接下来就可以根据真实关系数据库进行完整的测试了。
连接 Spring bean
至此,对于 Spring 框架什么时候和如何获得机会去实际处理 POJO,仍然不清楚。谜题的数据访问部分解决了,但仍有两个问题存在:Spring 2 引擎怎么知道要做什么,如何指定要使用哪个关系数据库?
立即就会解决这两个问题;将看到如何向 Spring 引擎提供 bean 连接模板。秘密在于叫作 dwspring-service.xml 的 XML bean 描述符文件。这个 bean 描述符文件是 Spring 2 框架操作概述 中提过的连接蓝本。它描述了 Spring 应用程序中不同的 bean 之间的关系。清单 11 显示了这个文件:
清单 11. dwspring-service.xml bean 描述符
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:dwspring"/>
<property name="username" value="sa" />
<property name="password" value=" " />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
要测试 EmployeeDAO 实现的动作,可以使用叫作 HSQLDB 的内存内(请参阅 参考资料)。HSQLDB 的可执行文件是 “有依赖项的 Spring 2” 下载的一部分。
在清单 11 中,专门配置 HSQLDB 实例的行用黑体表示。稍后(在 编写针对 RDBMS 的 DAO 集成测试)中,将看到如何修改这些行,来针对 DB2 Express-C 运行集成测试。
请记住 EmployeeDAO 实际上扩展了 JpaDaoSupport 类。这个类希望在装入的时候被 JPA EntityManagerFactory “插入”。然后它可以用这个工厂得到所有数据访问操作的 JPA EntityManager。
图 6 以图形方式显示 bean 在 dwspring2-service.xml 文件中如何连接在一起:
图 6. bean 连接示意图
实际上,清单 11 是需要由 Spring 2 引擎创建的对象的连接计划,图 6 是这些对象的图表。在清单 11 和图 6 中要注意的一个重要项目就是 EmployeeDAO 如何通过叫作 employeeService 的bean 获得。这个实例把自己的 entityManagerFactory 属性设置成另一个名为 entityManagerFactory的 bean:
[img]http://dl.iteye.com/upload/attachment/259545/a8c87a62-2556-3a79-b4d9-7bc44ed431c7.gif[/img]
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
ref="" 标志是对上下文中定义的另一个 bean 的引用 —— 通常是在同一个文件中。
3. 依赖性注入
用外部创建的对象来填充属性,就像刚才做的那样,这叫作 注入——更具体地讲,叫作依赖性注入 (DI),因为被注入的对象通常是接收对象进行正确操作所依赖的事物。DI 在 Spring 中框架中使用得很多。使用 DI,编写组件代码时不需要主动查询或查找依赖服务(例如,查询 EntityManagerFactory)。相反,可以就像依赖服务已经存在一样地编写组件代码,Spring 引擎会在代码执行之前把实际的依赖性注入组件。
依赖性注入的应用程序
如果查看 清单 11 中一直到 entityManagerFactory 连接的部分,您会注意到 Spring 注入了以下依赖项:
dataSource
jpaVendorAdapter
loadTimeWeaver
dataSource bean 是 org.springframework.jdbc.datasource.DriverManagerDataSource 的实例,用 HSQLDB RDBMS 的内存中实例进行了配置。
jpaVendorAdapter 属性通过连接到 Spring 应用程序实际 JPA 实现的 bean 注入。在这个示例中,使用的是 JPA 引用实现,通过 org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter 类来访问。这个类接着需要用 databasePlatform property 进行配置。这个属性被设置成 oracle.toplink.essentials.platform.database.HSQLPlatform,此配置支持对 HSQLDB RDBMS 的访问。这个 bean 的 generateDdl 属性控制着是否生成和执行数据定义语言脚本。如果这个属性设为 true,那么这个 bean 每次装入时,都会重新建立数据库中的方案。为了集成测试的目的,应当保持这个属性为 true。
在 dataSource bean 的配置中,创建了 org.springframework.jdbc.datasource.DriverManagerDataSource 的实例。它的参数设置有:
HSQLDB 数据库驱动程序
创建内存中数据库的 JDBC UR(JDBC URL 的 mem 部分)
用户名和口令(对 HSQLDB,默认分别是 sa 和 "")
最后一个 transactionManager bean 是为后面的集成测试配置的。不需要连接这个 bean,因为后面要用的测试基类会按类型查找这个 bean。
至此,您应已对 Spring 2 如何连接 bean 有了一定的连接。您还应了解如何把数据库从 HSQLDB 转换到 DB2 Express-C,这一步要在下一节进行)
4. 现在还剩下的唯一问题是:如何和在什么时候调用 Spring 2 引擎,它怎么知道该如何使用 dwspring2-service.xml 配置文件?
看一下 EmployeeServiceIntegrationTest.java 中的集成测试源代码,答案就一目了然了。这个集成测试针对实际的 RDBMS 测试 EmployeeService 的 EmployeeDAO 实现。请参阅清单 12 中的代码片断:
清单 12. EmployeeServiceIntegrationTest 中的集成测试(第 1 部分)
package com.test.dw.spring2;
import java.util.Date;
...
import org.springframework.test.jpa.AbstractJpaTests;
public class EmployeeServiceIntegrationTest extends AbstractJpaTests {
private EmployeeService employeeService;
private long JoeSmithId = 99999;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
protected String[] getConfigLocations() {
return new String[] { "classpath:/com/ibm/dw/spring2/dwspring2-service.xml" };
}
这套集成测试是在 Spring 2 库的 AbstractJpaTests 类的帮助下编写的。通过实现清单 12 中强调的 getConfigLocations() 方法,可以提供一个或多个需要由 Spring 2 引擎解析的 bean 配置文件。可以有多个配置文件,因为分离后端和用户界面 bean 配置文件是一种常见实践。
清单 12 中的 setEmployeeService() 是依赖性注入的示例。当 Spring 2 引擎装入这个 EmployeeServiceIntegrationTest 类时(派生自 AbstractJpaTests),它发现一个没有填充的依赖项 —— 类型为 EmployeeService 的属性。引擎就查找 dwspring2-service.xml 文件,查找配置好的类型为 EmployeeService 的bean,并通过 setEmployeeService() 方法注入。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
按类型自动连接
您可能注意到,在 dwspring2-service.xml 文件中,对于 employeeService 的注入缺少明确的注入指令。实际上,这个注入是自动发生的。在 Spring 的术语中,这叫作自动连接。
AbstractJpaTests 基类派生自 AbstractDependencyInjectionSpringContextTests 类。 AbstractDependencyInjectionSpringContextTests 通过把 Spring 的按类型自动连接特性设为默认,简化了测试。如果在应用程序的上下文(在这个示例中,由 dwspring2-service.xml 文件配置)中发现相同类型的 bean,那么它的子类(EmployeeServiceIntegrationTest 就是这样一个子类)的任何依赖项(公共属性)就被自动注入。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
集成测试设置
AbstractJpaTests 针对测试的一个有用特性,就是在事务中执行测试,然后在测试后回滚所有效果的能力。这切实地加快了测试,因为在每次测试运行期间,不再需要删除和重建数据了。
清单 13 显示了执行每个测试的初始设计的代码。这个设置代码用三个 Employee 和它们的相关 Addresse 填充数据库。必须在与每个测试相同的事务内执行这个代码。否则,就不会看到注入的数据,因为事务总会被回滚。要在相同事务中执行设置,要覆盖 onSetUpInTransaction() 方法,如清单 13 所示:
清单 13. EmployeeServiceIntegrationTest 中的集成测试(第 2 部分)
protected void onSetUpInTransaction() throws Exception {
Employee emp1 = new Employee("0001", "Joe", "R","Smith",
"4853", "Engineer", 3, 'M',
20000.00, 0.00, 0.00,
new Address(10, "Walker Street")
, new Date(), new Date());
Employee emp2 = new Employee("0002", "John","T","Lockheed",
"4333", "Sales", 2, 'M',
40000.00, 0.00, 5000.00,
new Address(20, "Walker Street")
, new Date(), new Date());
Employee emp3 = new Employee("0003", "Mary","M","Johnson",
"4383", "Admin", 3, 'F',
60000.00, 0.00, 390.00,
new Address(123, "Booth Ave")
, new Date(), new Date());
employeeService.save(emp1);
employeeService.save(emp2);
employeeService.save(emp3);
JoeSmithId = emp1.getEmpid();
}
请注意,通过基于 JPA 的 employeeService 创建并保持 Employee 实例有多简单。因为 save() 方法调用 JPA 的 persist() 操作,而且操作会从 Employee 级联到 Address 对象(在 Employee POJO 的 JPA 注释指定这个),所以也可以依靠 JPA 在地址表中创建新记录。
各测试每次执行时,都会执行 onSetUpInTransaction() 中的设置代码。这确保了在每次测试之前,都有三个员工。
作为测试示例,清单 14 显示了 EmployeeDAO 的 update() 方法的测试方法 testModifyEmployee():
清单 14. EmployeeServiceIntegrationTest 的集成测试(第 3 部分)
public void testModifyEmployee() {
String oldLastName = "Lockheed";
String newLastName = "Williams";
Employee emp = employeeService
.findByEmployeeLastName(oldLastName)。get(0);
emp.setLastName(newLastName);
Employee emp2 = employeeService.update(emp);
assertEquals(newLastName, emp2.getLastName());
List<Employee> results = employeeService
.findByEmployeeLastName(oldLastName);
assertEquals(0, results.size());
results = results = employeeService
.findByEmployeeLastName(newLastName);
assertEquals(1, results.size());
}
清单 14 中的测试用例 testModifyEmployee() 用 EmployeeService 的 update()把员工“John Lockheed”的姓氏从“Lockheed”改成“Williams”。然后它调用 findByEmployeeLastName("Williams"),检验最后有一个员工“Williams”。它还检验没有姓氏为“Lockheed”的员工存在。
EmployeeServiceIntegrationTest 中还有其他许多测试。可以研究这些代码查看如何利用 EmployeeService 实现上的方法操纵实际数据(请参阅 下载)。
接下来,设置根据 RDBMS 运行这些集成测试的环境。
----------------------------------------------------------------------
----------------------------------------------------------------------
-----------
包含 persistence.xml 文件
虽然在这个 Spring JPA 集成场景中, persistence.xml 文件没有提供配置信息,但 JPA 规范要求这个文件。
persistence.xml 文件是持久性单元 的说明。在 JPA 术语中,持久性单元包含 EntityManagerFactory (和相关的配置信息),它创建的 EntityManagers 和这些 EntityManager 管理的类(以及这些类的元数据,以注释或 XML 形式)。
在这个示例中,persistence.xml 文件非常简单,如清单 15 所示:
清单 15. META-INF/persistence.xml 文件
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="dwSpring2Jpa" transaction-type="RESOURCE_LOCAL"/>
</persistence>
在某些厂商的配置中,persistence.xml 文件可以包含相关的说明信息,但是 Spring/JPA 集成中没有。只需要确定具有一份 META-INF/persistence.xml 的副本即可。
[b]下面顺便说下在Jboss环境中操作JPA。[/b]
1. persistence.xml 文件需包含比较多的内容:
<?xml version="1.0" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="slam-repository">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/sla_repository</jta-data-source>
<!-- 所以得PO 类 -->
<class>com.test.PO.xx.java</class>
。。。
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.jdbc.fetch_size" value="100" />
<property name="hibernate.jdbc.batch_size" value="50" />
<property name="jboss.entity.manager.factory.jndi.name"
value="java:/testEntityManagerFactory" />
</properties>
</persistence-unit>
</persistence>
2. 代码中获得 EM
try {
InitialContext ic = new InitialContext();
xts = (TransactionManager) ic.lookup("java:/TransactionManager");
this.entityManagerFactory = (EntityManagerFactory) ic
.lookup("java:/slarepositoryEntityManagerFactory");
// entityManager = entityManagerFactory.createEntityManager();
} catch (Exception e) {
e.printStackTrace();
}
。。。
xts.begin();
EntityManager saveManager = this.saveManagerFactory
.createEntityManager();
saveManager.joinTransaction();
...
为 Spring 2 准备 Tomcat
在可以向 Tomcat 成功部署 dwspring.war 文件之前,需要进行一些服务器设置。
在这一节执行的主要过程有:
把 Spring 2 类装入器添加到 Tomcat
把 Spring 2 上下文装入器侦听器添加到 Tomcat
把 DB2 JDBC 驱动程序复制到 Tomcat
为 Tomcat 配置 JNDI DB2 数据源
把 Spring 2 类装入器添加到 Tomcat 服务器
当 Spring JPA 应用程序在 Tomcat 上运行时,要让 JPA 支持正常工作,需要在类装入期间进行字节码“连接”。来自 Tomcat 的标准类装入器不支持这个。需要用特定于 Spring 的类装入器实现这个功能。
要把这个特定于 Spring 的类装入器安装到 Tomcat 服务器,首先要把 spring-tomcat-weaver.jar 拷贝到 Tomcat 的 server/lib 子目录。这个目录包含的库属于 Tomcat 服务器私有。可以在 Spring 2.0 下载的 dist/weaver 目录下找到 spring-tomcat-weaver.jar 库。
接下来,必须让 Tomcat 知道对于示例应用程序,应当替换标准类装入器。可以在 WAR 文件的 META-INF/context.xml 文件中指定这点。清单 25 中的粗体代码配置类装入器:
清单 25. 在 META-INF/context.xml 文件中配置类装入器
<Context>
<Loader loaderClass="org.springframework.instrument.
classloading.tomcat.TomcatInstrumentableClassLoader"/>
...
</Context>
把 Spring 2 的上下文装入器侦听器添加到 Tomcat
Spring 2 要求挂接到 Tomcat 的上下文装入管道。可以在 WAR 文件的 WEB-INF/web.xml 文件添加以下行进行这个配置:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
在 web.xml 文件中,这必须在 <servlet> 和 <servlet-mapping> 定义之前。
在 Tomcat 5 上配置 DB2 数据源管理和 JNDI
可以把一个应用程序可以访问的 DB2 数据源配置成 Web 应用程序上下文的 JNDI 资源。方法是把清单 26 中突出的代码放在自己 WAR 的 META-INF/context.xml 文件中:
清单 26. 在 META-INF/context.xml 中配置 JNDI 资源
<Context>
...
<Resource name="jdbc/dwspring2" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="bill"
password="lotus123" driverClassName="com.ibm.db2.jcc.DB2Driver"
url="jdbc:db2://192.168.23.36:50000/dwspring"/>
</Context>
需要替换清单 26 中的 DB2 Express-C 服务器主机、用户名和口令,反映自己的 DB2 Express-C 安装情况。
清单 27 的配置通过名称 java:comp/env/jdbc/dwspring2 提供了 JNDI 数据源。
还必须向部署描述符 web.xml 文件添加资源引用。请把这个 <resource-ref> 元素添加到 web.xml 文件的末尾,如清单 27 所示:
清单 27. 添加数据源的 JNDI 资源引用
...
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/dwspring2</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
清单 27 的配置使得可以在 Web 应用程序中使用容器 JDBC 管理的数据源。
为 Tomcat 部署配置 Spring 2 应用程序
要为 Tomcat 部署配置数据层代码,仍然需要两个细节:
告诉 Spring 2 引擎关于 bean 描述符配置文件的位置
把 Spring 2 数据源配置的连接改为通过 JNDI 使用 Tomcat 的数据源管理和池管理
告诉 Spring 2 引擎配置文件的位置
要让 Spring 2 引擎连接需要的数据层 bean,首先必须中找到并处理数据层的 bean 描述符配置文件。
在这个示例中,文件叫作 dwspring-service.xml。需要在部署的时候,在提供给 Tomcat 服务器的上下文参数中指定这个文件的位置。
这个上下文参数需要是 WEB-INF/web.xml 部署描述符的第一个元素,在清单 28 中用粗体表示:
清单 28. web.xml 中的上下文参数
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
spring2web</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dwspring2-service.xml</param-value>
</context-param>
...
dwspring2-service.xml 是前面在测试时使用的配置文件的改动版本。
这个配置文件被修改成通过 JNDI 使用 Tomcat 服务器的 JDBC 连接管理,而不是自己的连接管理。
修改 Spring 2 连接查找 Tomcat JNDI 数据源
dwspring2-service.xml 中使用 Tomcat 5 得到 JNDI 数据源需要进行的修改在清单 29 中已突出显示:
清单 29. 修改 dwspring2-service.xml 以进行 Tomcat JNDI 查询
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="databasePlatform" value=
"oracle.toplink.essentials.platform.database.DB2Platform"/>
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/dwspring2" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
清单 29 中用于创建数据源的 bean 现在是 org.springframework.jndi.JndiObjectFactoryBean。可以用这个 bean 执行对特定的容器管理资源的 JNDI 查询。在这个示例中,Tomcat 管理的 DB2 Express-C 数据源的名称被配置成 java:comp/env/jdbc/dwspring2。
在清单 29 中,请注意 jpaVendorAdapter 的 GenerateDdl 属性被设置成 false。这是必需的,因为不想让 Spring 2 在每次启动应用程序时都删除和重建所有数据库表。只有在集成测试期间,这个属性才应设置成 true。