抢占先机
VMware 提供培训和认证,助您快速进步。
了解更多所有应用程序都源自域模型。"域模型"一词描述了系统中对您试图解决的问题至关重要的名词或数据。服务层——业务逻辑所在之处——处理应用程序数据,并最终需要将其持久化(通常在数据库中)。解释很简单,但在实践中,构建一个好的服务层对任何开发人员来说都可能是一项艰巨的任务。这篇文章将向开发人员介绍 Spring 框架中用于构建更好的服务层的可用选项。假定读者对 SQL 语言有一些经验,更重要的是,熟悉基本的 Spring 依赖注入和配置概念。此项目的源代码位于 SpringSource Git 仓库下Spring Samples 项目中 "showcases/green-beans/building-a-better-service-tier" 目录下。
这种数据——记录数量不深的相关数据——非常适合关系型数据库管理系统(通常称为 RDBMS)。RDBMS 通过将域模型映射到表来工作。我们服务使用的表如下图所示:
src/main/resources/crm.sql
)。DDL 很简单,只需做一些微不足道的调整即可在大多数数据库中工作。如果您想使用 H2,请按照以下说明进行设置,如果您尚未设置的话。否则,请随时跳到下一节“域模型”。H2 是一个轻量级的、嵌入式的内存 SQL 数据库,可以快速设置和运行。要开始使用,请从H2 主页下载最新版本。选择您喜欢的任何分发版(Windows 或“所有平台”),尽管出于本文的目的,我们将选择“所有平台”分发版。将分发版解压缩到您喜欢的文件夹中。在命令行中,导航到分发版中的 bin 文件夹,然后运行适合您平台的 shell 脚本(Windows 为 h2.bat,Unix 或 Linux 环境为 h2.sh),以启动数据库进程并启动一个可用于与数据库交互的 shell。在“JDBC URL:”字段中输入以下内容:jdbc:h2:tcp://127.0.0.1/~/crm_example
(不带引号),其余保持不变,然后单击“连接”按钮。在浏览器中打开 URL http://localhost:8082/login.jsp
以调出数据库控制台。H2 可以嵌入式运行(而不是像我们这样以服务器模式运行),但以服务器模式运行可以提供更丰富的体验。登录后,您就可以在 H2 控制台中尝试查询了。
package org.springsource.examples.crm.model;
…
public class Customer implements java.io.Serializable {
private Long id;
private String firstName;
private String lastName;
private Set purchases = new HashSet();
// constructors, and accessor / mutator pairs omitted for brevity
}
客户实体引用了 Purchase
,其定义如下:
package org.springsource.examples.crm.model;
…
public class Purchase implements java.io.Serializable {
private Long id;
private Customer customer;
private double total;
private Set lineItems = new HashSet();
// constructors, and accessor / mutator pairs omitted for brevity
}
Purchase 又引用了一组 LineItem
,其定义如下:
package org.springsource.examples.crm.model;
…
public class LineItem implements java.io.Serializable {
private Long id;
private Purchase purchase;
private Product product;
// constructors, and accessor / mutator pairs omitted for brevity
}
最后,LineItem
引用了一个 Product
。Product
是库存中的物品定义,定义如下:
package org.springsource.examples.crm.model;
…
public class Product implements java.io.Serializable {
private Long id;
private String description;
private String name;
private double price;
private Set lineItems = new HashSet();
// constructors, and accessor / mutator pairs omitted for brevity
}
Customer
记录。暂时忽略域模型中的其他实体。仓库应该隔离用户与用于处理持久化的原始 API。输入和输出应该是域模型对象,而不是较低级别的持久化原语。让我们看一下我们的 Customer
仓库的接口。package org.springsource.examples.crm.services.jdbc.repositories;
import org.springsource.examples.crm.model.Customer;
public interface CustomerRepository {
Customer saveCustomer(Customer customer) ;
Customer getCustomerById(long id);
}
我们将使用 JDBC 来构建我们的仓库。JDBC(Java 数据库连接 API)是 Java 平台提供的标准数据库连接框架。它的使用有详细的文档,所有主要供应商都为其数据库提供 JDBC 驱动程序。这似乎使 JDBC 成为构建仓库的自然起点。然而,在实践中,直接使用 JDBC 实现可能会很繁琐。
package org.springsource.examples.crm.services;
import org.springsource.examples.crm.model.Customer;
public interface CustomerService {
Customer getCustomerById(long id);
Customer createCustomer(String fn, String ln);
}
由于直接使用 JDBC 可能非常繁琐,我们将不再深入探讨。建议您查看本文的源代码,我们在其中构建了一个直接使用 JDBC 的仓库,需要令人头晕的 150 多行代码来处理我们将要介绍的内容,包括线程安全以及资源的获取和释放。
取而代之的是,让我们介绍一个 Spring 框架类,名为 JdbcTemplate
,它极大地简化了基于 JDBC 的开发。
为了开始,我们已经设置了一个标准的 Spring XML 文件,该文件依次设置了类路径组件扫描,并引入了用于解析 database.properties
文件中属性的属性占位符。这里不再重新打印 XML,因为它已经在源代码中(本文的示例 XML 文件位于 src/main/resources
下),并且代表了一个非常基本的 Spring 配置。Spring 类路径组件扫描又会拾取 Java 配置类,这将是我们在本文中重点关注和扩展的内容。基本的、通用的 Java 配置类名为 CrmConfiguration
,如下所示。CrmConfiguration
类仅配置了一个 javax.sql.DataSource
。
package org.springsource.examples.crm.services.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import javax.sql.DataSource;
@Configuration
public class CrmConfiguration {
@Value("${dataSource.driverClassName}")
private String driverName;
@Value("${dataSource.url}")
private String url;
@Value("${dataSource.user}")
private String user;
@Value("${dataSource.password}")
private String password;
@Bean
public DataSource dataSource() {
SimpleDriverDataSource simpleDriverDataSource = new SimpleDriverDataSource();
simpleDriverDataSource.setPassword(this.password);
simpleDriverDataSource.setUrl(this.url);
simpleDriverDataSource.setUsername(this.user);
simpleDriverDataSource.setDriverClass(org.h2.Driver.class);
return simpleDriverDataSource;
}
}
为了有效地使用 JDBC,我们将使用 Spring 的 JdbcTemplate
来最小化样板代码。JdbcTemplate
将隔离我们免受资源管理的困扰,并极大地简化使用 JDBC API 的过程。下面是基于 JdbcTemplate
实现 CustomerRepository
接口的配置。在配置中,我们定义了一个 JdbcTemplate
的实例。
package org.springsource.examples.crm.services.jdbc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springsource.examples.crm.services.config.CrmConfiguration;
import javax.sql.DataSource;
@Configuration
public class JdbcConfiguration extends CrmConfiguration {
@Bean
public JdbcTemplate jdbcTemplate() {
DataSource ds = dataSource(); // this comes from the parent class
return new JdbcTemplate(ds);
}
}
以下是基于 JdbcTemplate
的 CustomerRepository
实现。
package org.springsource.examples.crm.services.jdbc.repositories;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;
import org.springsource.examples.crm.model.Customer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Repository
public class JdbcTemplateCustomerRepository implements CustomerRepository, InitializingBean {
@Value("${jdbc.sql.customers.queryById}")
private String customerByIdQuery;
@Value("${jdbc.sql.customers.insert}")
private String insertCustomerQuery;
@Autowired
private JdbcTemplate jdbcTemplate;
public Customer getCustomerById(long id) {
return jdbcTemplate.queryForObject(customerByIdQuery, customerRowMapper, id);
}
public Customer saveCustomer(Customer customer) {
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
simpleJdbcInsert.setTableName("customer");
simpleJdbcInsert.setColumnNames(Arrays.asList("first_name", "last_name"));
simpleJdbcInsert.setGeneratedKeyName("id");
Map<String, Object> args = new HashMap<String, Object>();
args.put("first_name", customer.getFirstName());
args.put("last_name", customer.getLastName());
Number id = simpleJdbcInsert.execute(args);
return getCustomerById(id.longValue());
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.jdbcTemplate, "the jdbcTemplate can't be null!");
Assert.notNull(this.customerByIdQuery, "the customerByIdQuery can't be null");
Assert.notNull(this.insertCustomerQuery, "the insertCustomerQuery can't be null");
}
private RowMapper<Customer> customerRowMapper = new RowMapper<Customer>() {
public Customer mapRow(ResultSet resultSet, int i) throws SQLException {
long id = resultSet.getInt("id");
String firstName = resultSet.getString("first_name");
String lastName = resultSet.getString("last_name");
return new Customer(id, firstName, lastName);
}
};
}
在示例中,仓库类使用 @Repository 进行注解,它是 Spring 框架的一个原型(stereotype)注解,其功能与 Spring 框架的 @Component
原型(stereotype)注解等效(除了在这里我们无需担心的一些细微差异)。在这个特定的示例中,我们同样可以使用 @Component
。
第一个方法——getCustomerById(long)
——使用 jdbcTemplate
实例发出查询。JdbcTemplate
的 query
方法的第一个参数是 SQL 语句,第二个参数是 RowMapper<T>
的实例。RowMapper 是一个 Spring 接口,客户端可以实现它来处理将结果集数据映射到对象(在本例中是 Customer
的实例)。对于返回结果集中的每一行,JdbcTemplate
将调用 mapRow(ResultSet,int)
。
RowMapper 实例是无状态的(因此是线程安全的),应将其缓存以供任何其他映射 Customer 记录的查询使用。在 SQL 字符串和 RowMapper 实例之后,jdbcTemplate.queryForObject
方法支持 Java 5 的变长参数(varargs)语法,用于按数字顺序绑定到查询中的参数:第一个变长参数绑定到查询中的第一个“?”,第二个绑定到第二个“?”,依此类推。
第二个方法插入一条记录(简单)然后检索新插入记录的 ID。我们使用 SimpleJdbcInsert
对象来描述我们的表和所需的参数,然后以独立于数据库的方式执行插入。
现在,我们有了一个可工作的仓库。仓库是一个“愚蠢”的对象。它不知道事务,也不理解业务逻辑。业务对象利用仓库实现并协调它们。业务对象拥有“全局视野”,而仓库只关心持久化您的域模型。在决定什么放在哪里时,请记住这一点。
首先,让我们看看我们的 CustomerService
接口。
package org.springsource.examples.crm.services;
import org.springsource.examples.crm.model.Customer;
public interface CustomerService {
Customer getCustomerById(long id);
Customer createCustomer(String fn, String ln);
}
这看起来与仓库接口相似,人们可能会想为什么还需要使用仓库。应该理解的是,服务关心的是业务状态,而不是应用程序状态。服务关心业务事件。例如,仓库关注将 Customer
记录持久化到数据库的机制,而服务则关注确保 Customer
记录处于有效状态,并且该 Customer
没有(例如)已经报名参加了公司的免费产品试用活动。因此,虽然服务和仓库看似都有一个看起来是“创建客户”的方法,但它们的职责和焦点应该非常不同。考虑到这一点,让我们看一下我们基于简单 JDBC 的 CustomerService 实现。
package org.springsource.examples.crm.services.jdbc.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springsource.examples.crm.model.Customer;
import org.springsource.examples.crm.services.CustomerService;
import org.springsource.examples.crm.services.jdbc.repositories.CustomerRepository;
@Service
public class JdbcCustomerService implements CustomerService {
@Autowired
private CustomerRepository customerRepository;
public Customer getCustomerById(long id) {
return this.customerRepository.getCustomerById(id);
}
public Customer createCustomer(String fn, String ln) {
Customer customer = new Customer(fn, ln);
return this.customerRepository.saveCustomer(customer);
}
}
这个服务很直观。就像处理仓库一样,我们使用 Spring 注解原型 @Service
注解了这个服务。这个注解比 @Component
更能表达类的意图,但也没有理由不能使用 @Component
。典型的服务应该比仓库具有更粗粒度的方法,因此通常会看到服务使用多个仓库。服务协调多个仓库。这意味着服务需要确保跨多个仓库、跨多个客户端的状态一致性。不难想象状态不一致会带来的灾难。假设您有一个服务,负责管理电子商务网站上用户的购物车结账。当用户点击“提交订单”时,服务需要从库存中预留购物车中的所有订单项,并从用户的账户中扣款。如果,同时有另一个用户试图结账同一件商品(不幸的是库存中只剩下一件),并且足够快地完成了结账流程,那么第一个用户就会为一个商家无法交付的商品付款了!
单个操作——也许是查询和更新——转化为单个的 jdbcTemplate
调用。这些单个调用可以通过将 Spring 的 TransactionTemplate
与 Spring 的 PlatformTransactionManager 层次结构的实例一起使用,来共享同一个事务。然后,TransactionTemplate
使用事务管理器来启动和提交所需的事务。Spring 框架提供了 TransactionTemplate
来提供事务同步。我们的服务只使用一个事务资源——一个 javax.sql.DataSource
——所以一个 DataSourceTransactionManager
实例就足够了。将以下内容添加到 JdbcConfiguration
类中。
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(this.dataSource());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate transactionTemplate() {
TransactionTemplate tt = new TransactionTemplate();
tt.setTransactionManager( this.transactionManager() );
return tt;
}
使用事务模板执行服务类中应包含在事务中的逻辑。服务代码的关键部分更改如下所示。
@Autowired
private TransactionTemplate transactionTemplate;
public Customer getCustomerById(final long id) {
return this.transactionTemplate.execute(new TransactionCallback() {
public Customer doInTransaction(TransactionStatus status) {
// … all the same business logic as before
}
});
}
public Customer createCustomer( final String firstName, final String lastName) {
return this.transactionTemplate.execute(new TransactionCallback() {
public Customer doInTransaction(TransactionStatus status) {
// … all the same business logic as before
}
});
}
大功告成!JdbcTemplate
使处理 JDBC 变得非常简单,而 TransactionTemplate
使事务管理轻而易举。这个实现比我们手动完成的任何事情都要简单得多。我会称之为成功。
然而,我们可以做得更好。代码的许多演进都是为了从代码中移除横切关注点。在可能的情况下,Spring 在 API(如 JDBC)之上提供了更简单的抽象。Spring 还支持通过面向方面编程(AOP)系统地应用有用行为来引入功能。在前面的示例中,如果您仔细看,就会清楚地看到 transactionTemplate
只是在事务上下文中包装了方法本身的执行——而不是方法执行的任何单个部分。当方法执行开始时,会创建一个事务或重用现有事务。当方法执行结束时,如果不是嵌套事务,则会提交事务。任何可以用方法执行边界来描述的问题,都可以通过 AOP 方法得到很好的解决。毫不奇怪,Spring 内置了基于 AOP 的事务支持,可以在方法执行的边界上启动和提交事务。
要启用 Spring 的事务支持,请将以下内容添加到您的 Spring XML 配置文件中:
<tx:annotation-driven transaction-manager = "transactionManager" />
这个 <tx:annotation-driven/>
引用了在 Java 配置中配置的 transactionManager
bean。此声明开启了 Spring 框架中的功能,该功能会检测您的服务 bean 方法上是否存在 Spring 的 @Transactional
注解。transactionTemplate
的引用变得无关紧要,可以在配置类和实现类中移除。剩下的就是将 @Transactional
添加到服务方法定义中。
@Transactional
注解可以参数化以自定义事务行为。getCustomerById
方法被注解为 @Transactional(readOnly = true)
,因为该方法不修改数据库中的任何内容。将其设置为 readOnly
只是告诉 Spring 框架无需创建事务。事务的创建并非总是廉价的,应谨慎使用。有关 Spring 框架中事务支持的更多信息,建议读者查看 Juergen Hoeller(非常棒)关于事务的录制演示文稿。
修改后的实现如下所示:
@Transactional(readOnly = true)
public Customer getCustomerById(final long id) {
// … same as before, with transactionTemplate removed
}
@Transactional
public Customer createCustomer(final String firstName, final String lastName) {
// … same as before, with transactionTemplate removed
}
CustomerService
实现。通过使用 JDBC,我们已经完成了所有可能的操作,以便能够根据我们的域模型清晰地与数据存储进行交互。这些示例之所以简单,是因为到目前为止,我们尝试做的事情很简单。我们只处理一种类型的对象——Customer——并且尚未开始编写处理诸如 Customer 购买之类的关系的代码。回想一下,购买有订单项,订单项引用产品。即使在我们的简单域中,这种对象图也可能很深。虽然确实可以使用对象的方式来处理数据库表,但这并不自然。数据库强制要求的模型(行、列和外键)与我们的域模型之间的这种分裂称为对象关系阻抗不匹配,这对于所有面向对象的语言(不只是 Java)来说都很常见。Java 持久化 API (JPA) 标准化了对象关系映射技术 (ORM)。ORM 通常通过对象类型和数据库表之间的映射,提供一种干净的方式来根据此映射持久化、操作和查询对象。在 JPA 中,这种映射主要通过域类本身的元数据注解驱动。JPA 实现通常支持多个数据库供应商。要开始使用 JPA,您应该已经选择了一个数据库(前面的示例已经使用了 H2 数据库,所以我们将继续使用它)和一个 JPA 实现。有许多不同的 JPA 提供商。许多 JPA 实现都与早于标准的其他 ORM 解决方案捆绑在一起。出于本解决方案的目的,我们使用 Hibernate JPA 实现。
让我们修改第一个示例以使用 JPA。首先修改 Customer 类,使其包含正确的由注解驱动的 JPA 引擎元数据。元数据是使用默认值派生的,如果需要明确配置,则使用 Java 语言注解。在以下代码中,我省略了修改器(mutators)。
package org.springsource.examples.crm.model;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "customer")
public class Customer implements java.io.Serializable {
// private variables and mutators omitted
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
public Long getId() {
return this.id;
}
@Column(name = "first_name", nullable = false)
public String getFirstName() {
return this.firstName;
}
@Column(name = "last_name", nullable = false)
public String getLastName() {
return this.lastName;
}
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
public Set getPurchases() {
return this.purchases;
}
}
使用 @Entity
注解的类会注册到 JPA 实现中。由于我们的 CustomerService
实现使用的是预先存在的数据库模式,因此我们添加了 @Table
注解,并指定将此类映射到哪个特定表。接下来,使用 @Id
注解指定哪个字段映射到表的主键。@GeneratedValue
注解告诉 JPA 期望此列将由数据库系统自动递增(或生成)。@Column
注解在此类中是多余的,因为 JPA 引擎会根据类中的 JavaBean 风格属性自动推断列名,但如果存在不匹配,则可以使用它来控制 JavaBean 属性如何映射到表中的列名。
购买集合的修改器上的注解是最重要的,因为它描述了一种关系。这个注解——@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
——虽然有点密集,但功能强大!这个注解告诉 JPA 引擎,有零个或多个 Purchase 对象属于这个 Customer
对象。JPA 引擎知道所有具有与当前 Customer 对象相同 ID 的 "customer"
属性(类型为 Customer
)的 Purchase
对象都属于这个客户。在数据库术语中,这些 Purchase 对象具有引用此客户记录的外键,这由 mappedBy = "customer."
表示。因此,Customer 类有一个用于 Purchases 的 JavaBean 属性(一个集合),而 Purchase
有一个用于 Customer
的 JavaBean 属性。 reciprocal 映射摘自下面的 Purchase 类:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
public Customer getCustomer() {
return this.customer;
}
JPA 引擎知道 Purchase
上的 Customer
对象是通过查看 PURCHASE
表的 CUSTOMER_ID
列,然后加载一个 Customer
实例而获得的。让我们重新审视 CustomerService
实现,看看它相对于基于 JdbcTemplate
的实现如何改进。首先,新的配置:
package org.springsource.examples.crm.services.jpa;
import org.springframework.context.annotation.*;
import org.springframework.orm.jpa.*;
import org.springframework.transaction.PlatformTransactionManager;
import org.springsource.examples.crm.services.config.CrmConfiguration;
import javax.persistence.EntityManagerFactory;
@Configuration
public class JpaConfiguration extends CrmConfiguration {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(this.dataSource());
return localContainerEntityManagerFactoryBean;
}
// this is required to replace JpaTemplate's exception translation
@Bean
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor = new PersistenceExceptionTranslationPostProcessor();
persistenceExceptionTranslationPostProcessor.setRepositoryAnnotationType( Service.class);
// do this to make the persistence bean post processor pick up our @Service class. Normally it only picks up @Repository
return persistenceExceptionTranslationPostProcessor;
}
@Bean
public PlatformTransactionManager transactionManager() {
EntityManagerFactory entityManagerFactory = entityManagerFactory().getObject();
return new JpaTransactionManager(entityManagerFactory);
}
}
事务管理器的实现是 JpaTransactionManager
,这是一个 PlatformTransactionManager
实现,它知道如何管理 JPA 局部事务。LocalContainerEntityManagerFactoryBean
创建 javax.persistence.EntityManagerFactory
的实现,这是一个 JPA 类,可用于创建 javax.persistence.EntityManager
的实例,后者是使用 JPA API 与数据源交互的核心 API。这个 API 是您使用 JPA 进行任何操作的关键。JPA 提供的简洁性使得一个适当的仓库对象看起来有些多余。毕竟,仓库的全部价值在于它让客户端能够根据域模型处理持久化问题,而 JPA 已经做到了这一点。如果您有非常复杂的持久化需求,仍然可以保留单独的层。然而,出于本文的目的,我们将利用 JPA 的简洁性,将仓库层折叠到服务层中。
您会注意到缺少一个模板类。Spring 确实提供了 JpaTemplate
,但最好让 Spring 直接为您注入一个 EntityManager。当您使用组件扫描(正如我们正在做的)时,Spring 会自动查找 @javax.persistence.PersistenceContext
(一个标准注解),并为您注入一个配置好的 EntityManager
实例代理。为什么是代理?因为 EntityManager
不是线程安全的,所以 Spring 会做大量的底层工作,以确保不同的客户端请求可以使用一个线程本地的 EntityManager
实例。如果我们使用了 JpaTemplate
类,我们将受益于 Spring 的异常翻译。对于 Spring 框架提供的所有 ORM 模板类,Spring 会自动将特定于技术的(受检)异常翻译成 Spring ORM 包中的标准运行时异常层次结构(根位于 org.springframework.dao.DataAccessException
)。这样,您就可以以标准的方式处理代码中的不同异常。我们这里选择直接注入实体管理器,所以需要重新启用异常翻译。通过注册一个 PersistenceExceptionTranslationPostProcessor
来做到这一点,它将在没有 JpaTemplate
的情况下为我们处理异常翻译。
下面的代码表示基于 JPA 的服务。它并不比我们的 JdbcTemplateCustomerService
长多少,但它实现了与仓库和服务相同的功能!
以下是基于 JPA 的 CustomerService 实现代码。
package org.springsource.examples.crm.services.jpa;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springsource.examples.crm.model.Customer;
import org.springsource.examples.crm.services.CustomerService;
@Service
public class JpaDatabaseCustomerService implements CustomerService {
@PersistenceContext
private EntityManager entityManager;
@Transactional(readOnly = true)
public Customer getCustomerById(long id) {
return this.entityManager.find(Customer.class, id);
}
@Transactional
public Customer createCustomer(String fn, String ln) {
Customer newCustomer = new Customer();
newCustomer.setFirstName(fn);
newCustomer.setLastName(ln);
this.entityManager.persist(newCustomer);
return newCustomer;
}
}
不错!我们的实现已经成为您面前看到的那个精简的样本。引入语句的数量几乎和方法体的实际代码行数一样多!一旦外围问题消失,JPA 本身就能极大地帮助您根据域中的对象解决问题。注入的 EntityManager
代理将 JDBC 中许多冗长操作减少到一行代码,而且它是线程安全的!最后,Spring 基于 AOP 的事务支持轻松解决了通常复杂且充满并发挑战的业务对象中应用程序状态管理问题(只需一个注解即可!)。
Spring 框架还为许多其他数据持久化选项提供了类似的支持。这些选项都遵循本文中建立的支持的总体模板,因此如果您决定改用它们,会很容易上手。本文没有涵盖 Spring 为许多其他 ORM 解决方案(如 Hibernate、JDO、TopLink 等)提供的支持。例如,关于 Hibernate 支持的更多信息,您可以查看 Alef 在此主题上的精彩文章。本文也没有涉及 Spring Data 项目提供的广泛的 NoSQL 支持。本文中的示例逐步简化,并且越来越依赖于“约定优于配置”。随着示例在抽象层次上不断提升,总有方法可以充分利用底层 API 的强大功能。