Spring-data-jpa 查询

原创性声明:本文完全为笔者原创,请尊重笔者劳动力。转载务必注明原文地址。

背景

要认识spring-data-jpa,就要和jpa(Java Persistence API:java 持久层API) 区分开来。这是两个不同的东西。jpasun公司官方的ORM框架,严格上说,是java ORM的一种规范,从jpa的组成基本都是一些接口类就可见一斑,而具体的实现更多的由第三方ORM框架完成,像Hibernate、TopLink、JDO等。因此可以说,jpa是一种规范标准,hibernate是具体的实现。sun公司之所以这样干,也是为了统一。

Spring-data-jpa又是什么呢?在企业级java EE开发中,Spring的地位已经难以撼动,它几乎无所不能。而它的强大更多的体现在与第三方框架的整合上,对于持久化这一块,Spring不仅仅可以很好的整合hibernate,它自己也开了'自营业务',于是就有了Spring-data-**等各种用于持久化操作的包,包括Spring-data-jpa、Spring-data-mongodb、Spring-data-template等。

而此文就是记录其中的Spring-data-jpa

在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。我们一般按照三层结构来看的话,Service层做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,所有的基础CRUD框架都提供,我们使用起来感觉很方便,很给力,业务逻辑层面的处理ORM是没有提供的,如果使用原生的框架,业务逻辑代码我们一般会自定义,会自己去写SQL语句,然后执行。在这个时候,Spring-data-jpa的威力就体现出来了,ORM提供的能力他都提供,ORM框架没有提供的业务逻辑功能Spring-data-jpa也提供,全方位的解决用户的需求。

开始
  1. 引入(以maven为例)


    org.springframework.data
    spring-data-jpa
    2.0.0.M4

如果使用的是Spring boot,那么可以这样引用:


  org.springframework.boot
  spring-boot-starter-data-jpa

2.例如,我们有一个实体类Article,如下:

@Entity
@Table(name = "articles")
public class Article extends AbstractAuditingEntity {

  @Id
  @Column(name = "id", unique = true, nullable = false)
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
    
  @Column(name = "title", nullable = false)
  private String title;
    
  @Column(name = "content", nullable = false)
  private String content;

  @Column(name = "if_set_top")
  @ColumnComment("是否置顶")
  private boolean ifSetTop = false; //默认false  
  
  // 省略get/set 方法...
 }

3.我们需要创建一个接口去继承Spring-data-jpa提供的接口:

public interface ArticleRepository extends JpaRepository {

}

通常的,ArticleRepository的命名遵循约定俗称,其中指定的Article表明了映射的对象,Long指的是Article实体类的主键id类型。如此,就可以在这个接口类中,声明一些特定的方法,以满足简单的基本CRUD。

基本查询

参见一张从网上down下来的截图:

Spring-data-jpa 查询_第1张图片
spring-data-jpa简单查询.png

参见这张截图,可以很简单的写出针对Article的一些查询,只需要在上面的ArticleRepository中定义这些方法即可,而不需要任何的实现,这就是Spring-data-jpa的强大之一。

注意:1. 接口方法名的命名要严格遵循驼峰法则;2.有些方法在JpaRepository中就已经声明可以直接调用,例如findAll, findOne,save, saveAndFlush等。

复杂查询

Spring-data-jpa的复杂查询体现在强大的动态查询和分页查询上。

4.我们需要让ArticleRepository接口再继承一个接口: JpaSpecificationExecutor。如下:

public interface ArticleRepository extends JpaRepository, 
                                           JpaSpecificationExecutor
{ }

进入JpaSpecificationExecutor接口可以发现,一些用于动态查询和分页查询的方法:

T findOne(Specification spec);

List findAll(Specification spec);

Page findAll(Specification spec, Pageable pageable);

List findAll(Specification spec, Sort sort);

long count(Specification spec);

5.接下来,我们就可以在ArticleService中这样去调用ArticleRepository的方法了:

@Service
public class ArticleService {
  
  /**
   * 过滤文章,传入filter,和一个分页对象,filter可以匹配id和title。返回分页查询后的结果
   */
  public Page
filterArticleByIdOrTitle(String filter, Pageable pageable) { return articleRepository.findAll(new Specification
() { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { Predicate predicate = null; if (filter != null && !filter.trim().equal("")) { if (StringUtils.isNumeric(filter)) { predicate = cb.equal(root. get("id"), "%" + filter + "%"); } else { predicate = cb.like(root.get("title")), "%" + filter + "%"); } } return predicate; } }, new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort())); } }

观察上面的代码,articleRepository.findAll(Specification spec, Pageable pageable),方法传入了两个接口作为参数。我们在new了这两个参数之后,进一步重载了toPredicate方法,这个方法是实现过滤查询的核心,它相当于在拼接最终复杂查询的sql,因此,其中可以去实现更为复杂的查询方法,包括但不限于: 排序、 分组、 关联其他表等

predicate译文就是"断言"的意思,在sql中代指 where后面的内容。

事实上,我们可以创建一个ArticleSpecification类,来继承Specification接口,从而实现toPredicate方法。这样的做法更加的优雅。

6.创建ArticleSpecification类,用以继承Specification,并实现toPredicate方法。

public class ArticleSpecification extends AbstractSpecification
{ private final ArticleCriteria criteria; // 动态查询的条件包装类 public ArticleSpecification(ArticleCriteria criteria) { // 构造方法,用以传入criteria this.criteria = criteria; } @Override public Predicate toPredicate(Root
root, CriteriaQuery query, CriteriaBuilder cb) { // 实现toPredicate Predicate predicate = cb.conjunction(); List> expressions = predicate.getExpressions(); // 快速搜索: 匹配id或title if (StringUtils.isNotBlank(criteria.getFilter)) { if (StringUtils.isNumeric(filter)) { expressions.add(cb.equal(root. get("id"), "%" + criteria.getFilter()+ "%")); } else { expressions.add(cb.like(root.get("title")), "%" + criteria.getFilter() + "%")); } } // 高级搜索: 精确匹配id if (criteria.getId() != null) { expressions.add(cb.equal(root. get("id"), "%" + criteria.getFilter()+ "%")); } // 高级搜索: 模糊匹配title if (StringUtils.isNotBlank(criteria.getTitle())) { expressions.add(cb.like(root.get("title")), "%" + criteria.getFilter() + "%")); } return predicate; } }

上面这个类中,我们定义了一个属性ArticleCriteria 对象,它是一个条件包装类。如下:

public class ArticleCriteria { 
  private Long id;
  private String title;
  private String filter;
  private Boolean ifSetTop;
  // 省略get/set方法
}

这样在ArticleService中,我们就可以这样去修改:

@Service
public class ArticleService {

  /**
   * 过滤文章,传入filter,和一个分页对象,filter可以匹配id和title。返回分页查询后的结果
   */
  public Page
filterArticleByIdOrTitle(ArticleCriteria criteria, Pageable pageable) { return articleRepository.findAll(new ArticleSpecification(criteria), pageable); } }

到这里,后端基本完成了。前端,我们就可以在页面视图上提供两种查询:简单查询高级查询
简单查询下,只需提供一个 filter对应的输入框,而高级查询下,提供两个输入框,一个对应id,一个对应title。

7.在客户端,我们只需要构造这样的POST请求即可(以angular为例):

$scope.query = { // 搜索的条件
    filter: '',
    page: 1,
    size: 10
}
$http.post("api/article", {
  filter: $scope.query.filter,
  page: $scope.query.page - 1,
  size: $scope.query.size
}).success(function(){
  // todo
})

至于数据如何与视图绑定,这里就不讲了。

angular页面分页的组件可以使用angular materialangular-material-data-table

补充

如果我们查询的结果进行排序,例如根据id倒序,但是希望ifSetTop为true的排在前面。可以在toPredicate方法中这样去构造Predicate

public class ArticleSpecification extends AbstractSpecification
{ private final ArticleCriteria criteria; // 动态查询的条件包装类 public ArticleSpecification(ArticleCriteria criteria) { // 构造方法,用以传入criteria this.criteria = criteria; } @Override public Predicate toPredicate(Root
root, CriteriaQuery query, CriteriaBuilder cb) { // 实现toPredicate Predicate predicate = cb.conjunction(); List> expressions = predicate.getExpressions(); // 快速搜索: 匹配id或title if (StringUtils.isNotBlank(criteria.getFilter)) { if (StringUtils.isNumeric(filter)) { expressions.add(cb.equal(root. get("id"), "%" + criteria.getFilter()+ "%")); } else { expressions.add(cb.like(root.get("title")), "%" + criteria.getFilter() + "%")); } } // 高级搜索: 精确匹配id if (criteria.getId() != null) { expressions.add(cb.equal(root. get("id"), "%" + criteria.getFilter()+ "%")); } // 高级搜索: 模糊匹配title if (StringUtils.isNotBlank(criteria.getTitle())) { expressions.add(cb.like(root.get("title")), "%" + criteria.getFilter() + "%")); } // 利用query去排序(也可以做更复杂的逻辑处理) query.where(predicate); List orders = new ArrayList<>(); orders.add(cb.desc(root.get("ifSetTop"))); orders.add(cb.desc(root.get("id"))); query.orderBy(orders); return query.getRestriction(); } }

涉及多表的复杂查询,例如,Article关联了另一个实体Book,需要根据BookId,过滤Article,或者过滤出不属于任何一本书的Article。则需要进一步熟练的在toPredicate方法中,构造predicate

在上面的代码中,例如:

root.get("id")

也可以通过JPA的原模型对象来访问:

root.get(Article_.id)

前提是,在这之前,需要引入pom依赖:



  org.hibernate
   hibernate-jpamodelgen
  true

再用maven重新编译即可生成Entity_这样的原模型对象。

关于spring-data-jpa的更多内容,推荐这篇博客

你可能感兴趣的:(Spring-data-jpa 查询)