在我们刚刚发布项目Spring Data JPA的第一个里程碑时,我想给你一个关于它的简要介绍.正如你所知道的,Spring framework 对于基于JPA的数据存取层提供了支持。那么 Spring Data JPA 是如何添加到Spring中的呢?回答这个问题,我想从一个数据存取组件开始。这个组件提供了一个简单的域(domain),它是用纯JPA和 Spring实现的,而且可以扩展和改进。在我们实现之后,我将用Spring Data JPA 来重构它。你在以在 GitHub上找到这个小项目的每一次重构的详细指导。
域(The domain)
为了保持简单,我从最简单常用的域开始:客户(Customer)和帐号(Account)
- @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
- }
帐户只有一个到期日(expriyDate).再无其他. - 它使用JPA注解.现在我们来看看管理帐号的组件.
- @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();
- }
- }
为了在后面重构引入存储层(repository layer)时不引起名称冲突,我特意命名为 class*Service.但是在概念上,这个类是一个存储对象,而不是服务.事实上我们有吗?
这个被@Repository 注释的类抛出的异常可以被Spring的DataAccessException捕获。另外我们还用到了@Transactional 来保证 save(...) 操作的事务性和该类其他方法(这里是findByCustomer(...))的事务只读标识。这样会对数据库性能和我们持久化提供器的性能有一定的优化。
由于我们不想让程序员去决定什么时候调用EntityManager的merge(...)或者persist(...)方法。我们用了实体类主键字段内容来判断到底调用哪一个。这是判断逻辑是全局通用的所以不用对每一个域对象都去声明实现。查询方法也是很直观的,我们写一个查询,绑定一个参数然后执行这条查询并取得结果。这个方法名其实已经很直观了,其实我们可以根据方法名就推出查询语句,所以这里是可以提升一下的。(在后面可以直接按照这样的模式写方法名和参数而不需要去写实现,SPRING DATA JPA会根据你方法的命名自动转成SQL。(如果配合良好的数据库设计和视图设计,省了不少事情))
Spring Data 存储库支持
在我们开始重构该实现之前,看一下示例项目中包含的那些在重构课程中能够运行的测试用例,确保那些代码现在依旧能用。下面我们就来看如何改善我们的实现类。
Spring Data JPA 提供了一个存储库编程模型,首先,每一个受管理的领域对象都要有一个接口:
- public interface AccountRepository extends JpaRepository<Account, Long> { … }
定义这个接口是出于两个目的:首先,通过继承JpaRepository我们获得了一组泛型的CRUD方法,使我们能保存Accounts,删除它们等等。其次,这使得Spring Data JPA存储库框架在classpath中扫描该接口,并为它创建一个Spring bean。
为了让Spring创建一个实现该接口的bean,你仅需要使用Sping JPA"命名空间"并用适当元素激活其对repository 的支持。
- <jpa:repositories base-package="com.acme.repositories" />
这将扫描包含在包(package)com.acme.repositories下所有的继承于JpaRepository的接口,并为该接口创建实现了SimpleJpaRepository的Sping bean。下面让我们迈出第一步,稍微重构我们的AccountService实现以便使用我们新介绍的repository接口。
- @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(...)实际委派给repository。在默认情况下,如果一个实体的主键属性为null,那么repository实现会将其作为新建实体,正如你在前面的例子中所看到的一样(注意,如果有必要,你可以对其进行更为详细的控制) 。除此之外,Spring Data JPA repository实现类已经被@Transactional标注的CRUD等方法可以摒弃@Transactional声明。
下一步,我们将重构查询方法。并且使查询方法与保存方法遵循相同的委派策略。我们在存储库接口里面引入查询方法并且将我们原有方法委托给新引进的方法:
- @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);
- }
- }
我们快速补充点事务处理的知识。在这种非常简单的情况下我们可以从AccountServiceImpl实现类中移除@Transaction注解,因为在存储库的CRUD方法已经进行了事务管理,查询方法在存储库接口中也已经用@Transactional(readOnly = true)进行了标注。当前设置——在service层次标记为事务性的方法是一个最佳实践(尽管在这里并不需要),因为当你在service层次查看方法时它可以显式清楚的表明操作是在同一个事务中处理的。除此之外,如果一个service层次的方法修改了,需要进行多次的存储库方法调用,这种情况下所有的代码任然将会在同一个事务中执行,因为在存储库内部的事务将会简单的加入到外部service层次的事务中。存储库事务行为和调整方式在 参考文档中有详细说明。
尝试再次运行测试案例,看看测试是否正常运行。打住,我们还没有实现forfindByCustomer()方法,是不?那它是如何工作的呢?
查询方法
当Spring Data JPA为创建AccountRepository接口创建Spring实例的时候,它会检查接口里面定义的所有查询方法并且 为它们每个都派生一个查询。默认情况下,Spring Data JPA 将自动解析方法名并以此创建一个查询,查询用标准JPA的API实现。在本例中findByCustomer(...)方法在逻辑上等同于JPQL 查询“select a from Account a where a.customer = ?1”。解析方法名称的解析器支持大量的关键字比如And,Or,GreaterThan,LessThan,Like,IsNull,Notand等等,如果您喜欢,您还可以添加ORDER BY子句。有关详情请参阅文档。这种机制给我们提供了一个查询方法编程模型就像你在Grails 或 Spring Roo中用到的一样。
现在,假设你想要显式的使用指定的查询。要做到这点你可以按照如下命名规约(本例中为Account.findByCustomer)在实体上通过注解或在orm.xml中声明一个JPA命名的查询来实现,另外一个选择就是你可以在存储库方法中使用@Query注解:
- @Transactional(readOnly = true)
- public interface AccountRepository extends JpaRepository<Account, Long> {
- @Query("<JPQ statement here>")
- 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条记录)。眼下我们通过两个整数适当限制查询来实现,但这样做有两个问题,两个整数实际上只代表了一个方面,这里并没有明确这一点。除此之外,结果我们只返回了一个简单的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方法支持分页并且查询执行机制知道分页的参数。在这个阶段,我们的包装类实际上已经过时了,因为客户端也可以直接调用我们的存储库接口,这样我们摆脱了整个的实现代码。
总结
在本文的课程中,我们把为存储库编写的代码减少到2个接口,包含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);
- }
- <jpa:repositories base-package="com.acme.repositories" />
我们拥有类型安全的CRUD方法、查询执行方法和内置的分页功能。这不仅适用于基于JPA的存储库,而且也适用于非关系型数据库,这很酷。第一个支持这些功能的非关系型数据库是MongoDB ,它是几天后发版的Spring Data Document中的一部分。你将会获得Mongo DB版的所有这些特性,而且我们还将支持其它一些数据库。另外,还有其它一些特性等待我们去探秘(比如,实体审计,自定义数据访问代码集成) ,我们会在后续的博文中涉及。
相关推荐
通过使用Spring Data JPA,开发者可以避免编写大量的JPA(Java Persistence API)和SQL代码,专注于业务逻辑。本笔记将深入探讨Spring Data JPA的核心概念、功能以及如何在实际项目中应用。 首先,我们需要理解JPA...
**Spring Data JPA** 是一个基于 **Java** 的开源框架,它是 **Spring Framework** 的一个模块,主要用于简化 **Java Persistence API (JPA)** 的使用。JPA 是 Java 平台上的一个标准,用于管理和持久化应用程序的...
Spring Data JPA API。 Spring Data JPA 开发文档。 官网 Spring Data JPA API。
本教程将详细介绍 Spring Data JPA 的核心概念与使用方法,帮助开发者从基础入门到实现复杂查询的完整过程。 第一章:Spring Data JPA 入门 Spring Data JPA 简化了 JPA(Java Persistence API)的开发,通过约定...
- **强大的查询支持**:除了简单的 CRUD 方法,Spring Data JPA 还支持基于方法名的复杂查询,甚至可以使用 JPA Querydsl 或 Specification 进行更复杂的查询。 - **事务管理**:Spring Data JPA 结合 Spring 的事务...
1. **简介**:了解Spring Data JPA的目标,它是如何简化JPA的使用,以及它如何与其他Spring模块集成。 2. **快速入门**:包括设置项目依赖,配置数据源和JPA供应商(如Hibernate),以及创建第一个Repository接口。...
"spring data jpa官方文档中文翻译"则提供了Spring Data JPA的详细指南,包括其设计原理、基本概念、使用方法和最佳实践。"JPA2.0官方文档"则深入讲解了JPA规范的各个方面,包括实体管理、查询、事务和并发控制等。 ...
这个教程压缩包“学习使用 Spring Data JPA 对 MySQL 进行操作.zip”显然是为了帮助开发者掌握如何利用该框架来高效地处理数据库操作。在这个教程中,我们将深入探讨以下几个核心知识点: 1. **Spring Data JPA ...
SpringBoot集成了SpringDataJPA之后,就可以利用SpringBoot的自动配置能力,让开发者能够快速搭建和运行使用SpringDataJPA的项目。 在SpringBoot项目中整合SpringDataJPA,首先要导入必要的Maven依赖。在项目中,...
【标题】"spring data jpa + spring + json demo"揭示了这个项目是关于使用Spring Data JPA、Spring框架以及JSON处理的一个示例应用。Spring Data JPA是Spring框架的一个模块,它简化了JPA(Java Persistence API)...
Spring Data JPA是Spring Framework的一个模块,它简化了JPA的使用。它提供了自动配置、基于方法的查询支持、Repository抽象层等特性,极大地提高了开发效率。通过定义简单的接口,Spring Data JPA可以自动生成对应...
Spring Data JPA 是 Spring 框架的一个模块,它为使用 JPA(Java Persistence API)提供了强大的支持,简化了数据访问层的开发。通过使用 Spring Data JPA,我们可以避免编写大量重复的 CRUD(创建、读取、更新、...
**SpringBoot整合SpringData JPA** 是一个现代Java开发中的常见技术栈,它结合了Spring Boot的便捷性和Spring Data JPA的数据访问效率。Spring Boot简化了应用的初始搭建以及配置,而Spring Data JPA则是Spring ...
在本项目"Spring Data JPA入门项目02"中,我们将深入探讨如何使用Spring Data JPA进行查询操作,包括排序和分页。Spring Data JPA是Spring Framework的一个模块,它为Java Persistence API (JPA) 提供了一种更加便捷...
然而,尽管Spring Data JPA带来了诸多便利,但在实际使用中也会遇到一些挑战和难点: 1. **SQL性能问题**: - Spring Data JPA虽然简化了查询编写,但可能导致生成的SQL不够优化,尤其是在处理大数据量或复杂查询...
Spring Data JPA 1.4.2版本的官方参考文档详细介绍了如何使用Spring Data JPA来简化数据持久化层的编码工作。 核心概念部分(Core concepts)涵盖了Spring Data JPA的基本概念,例如使用仓库(Repositories)、定义...
这是 《使用 Spring Data JPA 简化 JPA 开发》的sample code。原文在 http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/index.html
'SpringDataJPA从入门到精通'以SpringBoot为技术基础 从入门到精通 由浅入深地介绍SpringDataJPA的使用。有语法 有实践 有原理剖析。'SpringDataJPA从入门到精通'分为12章 内容包括整体认识JPA、JPA基础查询方法、...
通过Spring Data JPA,我们可以使用简单的查询方法定义,自动化的CRUD操作,以及Repository接口,大大减少了手动编写SQL和DAO层代码的工作量。 **3. MySQL** MySQL 是一款广泛使用的开源关系型数据库管理系统。在...
总结,Spring Data JPA是一个在Spring框架下实现数据访问层自动化的库,它简化了传统JPA使用过程中复杂和重复的代码编写,提高了开发效率,并为数据操作提供了统一的编程模型。通过深入了解和掌握Spring Data JPA,...