zoukankan      html  css  js  c++  java
  • SpringDataJPA

    Spring Data JPA

      自从用了Spring Data JPA之后个人感觉比hibernate、mybatis好用太多了---SpringData JAP非常契合OOP思想。如:设计模式的开放-封闭原则、依赖倒转原则、单一职责迪米特法则等等,也是OOP非常非常核心的东西(按住Ctrl+鼠标左键点击蓝色的可以查看对应模式)...我的GitHub上最近在更新一个Spring、SpringDataJPA、shiro框架整合的一个小项目,有兴趣可以瞅瞅;下面是我转载的一篇SpringDataJPA入门的文章,GitHub上还有一个Demo
      

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

    Spring Data repository support

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

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

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

    定义这个接口有两个目的:首先,通过扩展,JpaRepository我们得到了一些通用的CRUD方法到我们的类型中,允许保存Account,删除它们等等。其次,这将允许Spring Data JPA存储库基础结构扫描该接口的类路径并为其创建一个Spring bean

    为了让Spring创建一个实现此接口的bean,您只需使用Spring JPA命名空间并使用相应的元素激活存储库支持:

    <jpa:repositories 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(…)正确的实施?这个怎么用?

    Query methods

    当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("<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();
      }
    }
    

    到现在为止还挺好。现在剩下的是处理常见场景的两种方法:您不想访问给定查询的所有实体,而仅访问其中的一个页面(例如,页面1的页面大小为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方法,并且查询执行机制也知道Pageable参数。在这个阶段,我们的包装类实际上已经过时,因为客户端可能直接使用我们的存储库接口。我们摆脱了整个实施代码。

    Summary

    在本博客文章中,我们已经使用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的存储库,而且适用于非关系数据库。支持这种方法的第一个非关系数据库将作为Spring Data Data发布的一部分在几天内成为MongoDB。您将获得与Mongo DB完全相同的功能,并且我们也在为其他数据库提供支持。还有一些额外的功能需要探讨(例如,实体审核,自定义数据访问代码的集成),GitHub有个Demo。

    转载: 奥利弗吉尔克:http://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa/

  • 相关阅读:
    java学习——内部类、匿名内部类
    Java中接口之间的继承
    Settings
    POM
    Maven指令
    Maven生命周期
    内部类
    Modules
    Simple Maven Project
    Maven概述
  • 原文地址:https://www.cnblogs.com/uniquezhangqi/p/9199297.html
Copyright © 2011-2022 走看看