由于我们刚刚发布了Spring Data JPA项目的第一个里程碑,我想向您简要介绍其功能。您可能知道,Spring框架提供了构建基于JPA的数据访问层的支持。那么Spring Data JPA为这个基础支持添加了什么?为了回答这个问题,我想从使用普通JPA + Spring实现的示例域的数据访问组件开始,并指出留有改进空间的区域。在我们完成之后,我将重构实现以使用Spring Data JPA功能来解决这些问题区域。可以在Github上找到示例项目以及重构步骤的分步指南。
The domain
为了简单起见,我们从一个很小的知名域开始:我们Customer
有Account
s。
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
// … methods omitted
}
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
private Customer customer;
@Temporal(TemporalType.DATE)
private Date expiryDate;
// … methods omitted
}
该Account
有,我们将在稍后阶段使用的有效期限。除此之外,类或映射没有什么特别之处 – 它使用普通的JPA注释。现在让我们来看看组件管理Account
对象:
@Repository
@Transactional(readOnly = true)
class AccountServiceImpl implements AccountService {
@PersistenceContext
private EntityManager em;
@Override
@Transactional
public Account save(Account account) {
if (account.getId() == null) {
em.persist(account);
return account;
} else {
return em.merge(account);
}
}
@Override
public List<Account> findByCustomer(Customer customer) {
TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class);
query.setParameter(1, customer);
return query.getResultList();
}
}
我故意命名该类*Service
以避免名称冲突,因为我们将在开始重构时引入存储库层。但从概念上讲,这里的类是存储库而不是服务。那么我们实际上有什么呢?
该类注释@Repository
用于启用从JPA异常到Spring的DataAccessException
层次结构的异常转换。除此之外,我们使用@Transactional
以确保save(…)
操作在事务中运行并允许设置readOnly
-flag(在类级别)findByCustomer(…)
。这会导致持久性提供程序内部以及数据库级别的某些性能优化。
当我们想从决定是否拨打免费客户端merge(…)
或persist(…)
在EntityManager
我们使用id
的-场Account
来决定我们是否考虑一个Account
对象作为新的或不。当然,这个逻辑可以被提取到一个共同的超类中,因为我们可能不希望为每个域对象特定的存储库实现重复这个代码。查询方法也很简单:我们创建一个查询,绑定一个参数并执行查询以获得结果。这几乎让直行向前,人们可以把代码的执行作为样板与想象的有点是源自方法签名:我们预期List
的Account
s,查询非常接近方法名称,我们只需将方法参数绑定到它。你可以看到,还有改进的余地。
Spring Data存储库支持
在我们开始重构实现之前,请注意示例项目包含可以在重构过程中运行的测试用例,以验证代码是否仍然有效。现在让我们看看我们如何改进实施。
Spring Data JPA提供了一个存储库编程模型,该模型以每个托管域对象的接口开头:
public interface AccountRepository extends JpaRepository<Account, Long> { … }
定义此接口有两个目的:首先,通过扩展,JpaRepository
我们将一堆通用CRUD方法添加到我们的类型中,允许保存Account
s,删除它们等等。其次,这将允许Spring Data JPA存储库基础结构扫描此接口的类路径并为其创建Spring bean。
要让Spring创建一个实现此接口的bean,您需要做的就是使用Spring JPA命名空间并使用适当的元素激活存储库支持:
base-package="com.acme.repositories" />
这将扫描下面的所有包com.acme.repositories
以获取扩展的接口,JpaRepository
并为其创建一个Spring bean,并由其实现支持SimpleJpaRepository
。让我们迈出第一步,重构我们的AccountService
实现,使用我们新引入的存储库接口:
@Repository
@Transactional(readOnly = true)
class AccountServiceImpl implements AccountService {
@PersistenceContext
private EntityManager em;
@Autowired
private AccountRepository repository;
@Override
@Transactional
public Account save(Account account) {
return repository.save(account);
}
@Override
public List<Account> findByCustomer(Customer customer) {
TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class);
query.setParameter(1, customer);
return query.getResultList();
}
}
在重构之后,我们只需将调用委托save(…)
给存储库。默认情况下,如果实体的id
属性null
与上一个示例中的内容完全相同,则存储库实现会将实体视为新实体(注意,如果需要,您可以获得对该决策的更详细控制)。此外,我们可以删除@Transactional
该方法的注释,因为Spring Data JPA存储库实现的CRUD方法已经注释了@Transactional
。
接下来我们将重构查询方法。让我们遵循与save方法相同的查询方法委托策略。我们在存储库接口上引入了一个查询方法,并将原始方法委托给新引入的方法:
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
List<Account> findByCustomer(Customer customer);
}
@Repository
@Transactional(readOnly = true)
class AccountServiceImpl implements AccountService {
@Autowired
private AccountRepository repository;
@Override
@Transactional
public Account save(Account account) {
return repository.save(account);
}
@Override
public List<Account> findByCustomer(Customer customer) {
return repository.findByCustomer(Customer customer);
}
}
让我在这里添加关于事务处理的快速说明。在这个非常简单的情况下,我们可以完全删除类中的@Transactional
注释,AccountServiceImpl
因为存储库的CRUD方法是事务性的,查询方法标记为@Transactional(readOnly = true)
已经在存储库界面。当前设置,服务级别的方法标记为事务性(即使在这种情况下不需要),最好是因为在查看服务级别时,事务中发生的操作是明确的。除此之外,如果修改了服务层方法以对存储库方法执行多次调用,则所有代码仍将在单个事务中执行,因为存储库的内部事务将简单地加入在服务层启动的外部事务。存储库的事务行为以及调整它的可能性在参考文档中有详细记录。
尝试再次运行测试用例,看看它是否有效。停止,我们没有提供任何实施findByCustomer(…)
权利?这个怎么用?
查询方法
当Spring Data JPA为AccountRepository
接口创建Spring bean实例时,它会检查其中定义的所有查询方法,并为每个查询方法派生一个查询。默认情况下,Spring Data JPA将自动解析方法名称并从中创建查询。该查询使用JPA标准API实现。在这种情况下,该findByCustomer(…)
方法在逻辑上等同于JPQL查询select a from Account a where a.customer = ?1
。该分析方法名解析器支持相当大集如关键字And
,Or
,GreaterThan
,LessThan
,Like
,IsNull
,Not
等。OrderBy
如果您愿意,也可以添加子句。有关详细概述,请查看参考文档。这个机制为我们提供了一个查询方法编程模型,就像你以前从Grails或Spring Roo那样。
现在让我们假设您要明确要使用的查询。为此,您可以Account.findByCustomer
在实体或您的注释中声明遵循命名约定(在本例中)的JPA命名查询orm.xml
。或者,您可以使用以下方法注释存储库方法@Query
:
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
@Query("" )
List<Account> findByCustomer(Customer customer);
}
现在让我们进行前后比较CustomerServiceImpl
应用我们目前看到的功能:
@Repository
@Transactional(readOnly = true)
public class CustomerServiceImpl implements CustomerService {
@PersistenceContext
private EntityManager em;
@Override
public Customer findById(Long id) {
return em.find(Customer.class, id);
}
@Override
public List<Customer> findAll() {
return em.createQuery("select c from Customer c", Customer.class).getResultList();
}
@Override
public List<Customer> findAll(int page, int pageSize) {
TypedQuery query = em.createQuery("select c from Customer c", Customer.class);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
return query.getResultList();
}
@Override
@Transactional
public Customer save(Customer customer) {
// Is new?
if (customer.getId() == null) {
em.persist(customer);
return customer;
} else {
return em.merge(customer);
}
}
@Override
public List<Customer> findByLastname(String lastname, int page, int pageSize) {
TypedQuery query = em.createQuery("select c from Customer c where c.lastname = ?1", Customer.class);
query.setParameter(1, lastname);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
好的,让我们先创建CustomerRepository
并消除CRUD方法:
@Transactional(readOnly = true)
public interface CustomerRepository extends JpaRepository<Customer, Long> { … }
@Repository
@Transactional(readOnly = true)
public class CustomerServiceImpl implements CustomerService {
@PersistenceContext
private EntityManager em;
@Autowired
private CustomerRepository repository;
@Override
public Customer findById(Long id) {
return repository.findById(id);
}
@Override
public List<Customer> findAll() {
return repository.findAll();
}
@Override
public List<Customer> findAll(int page, int pageSize) {
TypedQuery query = em.createQuery("select c from Customer c", Customer.class);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
return query.getResultList();
}
@Override
@Transactional
public Customer save(Customer customer) {
return repository.save(customer);
}
@Override
public List<Customer> findByLastname(String lastname, int page, int pageSize) {
TypedQuery query = em.createQuery("select c from Customer c where c.lastname = ?1", Customer.class);
query.setParameter(1, lastname);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
到现在为止还挺好。现在剩下的是处理常见场景的两种方法:您不希望访问给定查询的所有实体,而只想访问它们的一页(例如,页面大小为10的第1页)。现在,这是通过两个适当限制查询的整数来解决的。这有两个问题。两个整数实际上代表了一个概念,这里没有明确说明。除此之外,我们返回一个简单的,List
所以我们丢失了有关实际数据页面的元数据信息:它是第一页吗?这是最后一个吗?总共有多少页?Spring Data提供了一个抽象,包括两个接口:( Pageable
捕获分页请求信息)以及Page
(捕获结果和元信息)。所以让我们尝试添加findByLastname(…)
到存储库接口并重写findAll(…)
,findByLastname(…)
如下所示:
@Transactional(readOnly = true)
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Page<Customer> findByLastname(String lastname, Pageable pageable);
}
@Override
public Page<Customer> findAll(Pageable pageable) {
return repository.findAll(pageable);
}
@Override
public Page<Customer> findByLastname(String lastname, Pageable pageable) {
return repository.findByLastname(lastname, pageable);
}
确保根据签名更改调整测试用例,但它们应该运行正常。这里归结为两件事:我们有CRUD方法支持分页,查询执行机制也知道Pageable
参数。在这个阶段,我们的包装类实际上已经过时了,因为客户端可能直接使用了我们的存储库接口。我们摆脱了整个实现代码。
概要
在这篇博文的过程中,我们通过3种方法和一行XML将已经为存储库编写的代码量减少到两个接口:
@Transactional(readOnly = true)
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Page<Customer> findByLastname(String lastname, Pageable pageable);
}
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
List<Account> findByCustomer(Customer customer);
}
base-package="com.acme.repositories" />
我们有类型安全的CRUD方法,查询执行和内置的分页。很酷的是,这不仅适用于基于JPA的存储库,也适用于非关系数据库。支持这种方法的第一个非关系型数据库将在几天内成为Spring Data Document发布的一部分。您将获得与Mongo DB完全相同的功能,我们正在努力支持其他数据库。还有其他功能需要探索(例如实体审核,自定义数据访问代码的集成),我们将通过这些功能在即将发布的博文中。
原文: https://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa