开始使用Spring Data JPA

由于我们刚刚发布了Spring Data JPA项目的第一个里程碑,我想向您简要介绍其功能。您可能知道,Spring框架提供了构建基于JPA的数据访问层的支持。那么Spring Data JPA为这个基础支持添加了什么?为了回答这个问题,我想从使用普通JPA + Spring实现的示例域的数据访问组件开始,并指出留有改进空间的区域。在我们完成之后,我将重构实现以使用Spring Data JPA功能来解决这些问题区域。可以在Github上找到示例项目以及重构步骤的分步指南。

The domain

为了简单起见,我们从一个很小的知名域开始:我们CustomerAccounts。

@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对象作为新的或不。当然,这个逻辑可以被提取到一个共同的超类中,因为我们可能不希望为每个域对象特定的存储库实现重复这个代码。查询方法也很简单:我们创建一个查询,绑定一个参数并执行查询以获得结果。这几乎让直行向前,人们可以把代码的执行作为样板与想象的有点是源自方法签名:我们预期ListAccounts,查询非常接近方法名称,我们只需将方法参数绑定到它。你可以看到,还有改进的余地。

Spring Data存储库支持

在我们开始重构实现之前,请注意示例项目包含可以在重构过程中运行的测试用例,以验证代码是否仍然有效。现在让我们看看我们如何改进实施。

Spring Data JPA提供了一个存储库编程模型,该模型以每个托管域对象的接口开头:

public interface AccountRepository extends JpaRepository<Account, Long> {  }

定义此接口有两个目的:首先,通过扩展,JpaRepository我们将一堆通用CRUD方法添加到我们的类型中,允许保存Accounts,删除它们等等。其次,这将允许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。该分析方法名解析器支持相当大集如关键字AndOrGreaterThanLessThanLikeIsNullNot等。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

 

Categories: Spring Data

发表评论 取消回复

开始使用Spring Data JPA_第1张图片

电子邮件地址不会被公开。

Name
Email
Website
What's on your mind?

近期文章
  • Maven deploy部署jar到远程私服仓库
  • java动态代理实现与原理
  • git 常用命令
  • java中观察者模式Observable和Observer
  • Netty解决TCP粘包和拆包问题的四种方案
近期评论
  • 马化腾发表在《Nginx的一些基本功能》
  • geyang发表在《世界,您好!》
  • 一位WordPress评论者发表在《世界,您好!》
分类目录
  • Big Data (5)
  • Java (27)
  • MicroServices (13)
    • GateWay (2)
    • REST (2)
  • Plus (38)
  • Spring (9)
    • Spring Boot (5)
    • Spring Data (4)
  • 中间件/框架 (5)
    • Kafka (3)
  • 数据库 (11)
    • Hbase (5)
    • MongoDb (2)
    • Mysql (3)
标签
apiDoc Drools dubbo fiddler Grafana hbase Hystrix IDEA java JDK jpa jvisualvm jvm kafka linux MongoDB MQTT Mysql Netty nginx OpenJDK Prometheus REST RocketMQ RPC Servlet Sleuth SOA spring boot spring data zookeeper Zuul 域名 微服务 数据结构 日志 爬虫 缓存 股票 设计模式 读书 运维 队列 集合 音乐
联系我

你可能感兴趣的:(开始使用Spring Data JPA)