SpringDataJPA之Specification复杂查询实战

目录
  • SpringDataJPA Specification复杂查询
    • 前言
    • 实现
    • Specification与Controller业务逻辑
    • ApiReturnUtil.page封装
    • 查询效果
    • 可能遇到的错误
    • JpaSpecificationExecutor接口
    • Specification
    • 一个一目了然的方法
    • Criteria+TypedQuery
  • 开发过程中JPA Specification的应用
    • 为什么需要Specification
    • 应用场景
    • JPA Specification实现复杂查询
    • JPA多条件、多表查询
    • Spring Data Jpa 简单模糊查询

SpringDataJPA Specification复杂查询

前言

继上次SpringData-JPA之ExampleMatcher实例查询使用一会之后发现ExampleMatcher对日期的查询特别糟糕,所以才有了Specification查询的研究。

  • 20200114:更新对JpaSpecificationExecutor的解析,Specification思路2,以及CriteriaBuilder +CriteriaQuery+Predicate+TypedQuery查询部分
  • 20180811:根据所学所用,重新更新了文章,并增加了Pageable分页排序功能。

实现

对应的Repository需要实现JpaSpecificationExecutor接口

public interface EventRepository extends JpaRepository<Event, Integer> , JpaSpecificationExecutor<Event>{

Specification与Controller业务逻辑

 @GetMapping("/event/list")
 public ApiReturnObject findAllEvent(String eventTitle,Timestamp registerTime,Integer pageNumber,Integer pageSize) {
  if(pageNumber==null) pageNumber=1;
        if(pageSize==null) pageNumber=10;
  //分页
        //Pageable是接口,PageRequest是接口实现,new PageRequest()是旧方法,PageRequest.of()是新方法
        //PageRequest.of的对象构造函数有多个,page是页数,初始值是0,size是查询结果的条数,后两个参数参考Sort对象的构造方法
        Pageable pageable = PageRequest.of(pageNumber,pageSize,Sort.Direction.DESC,"id");
        //Specification查询构造器
        Specification<Event> specification=new Specification<Event>() {
   private static final long serialVersionUID = 1L;
   @Override
   public Predicate toPredicate(Root<Event> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Predicate condition1 = null;
                if(StringUtils.isNotBlank(eventTitle)) {
                 condition1 = criteriaBuilder.like(root.get("eventTitle"),"%"+eventTitle+"%");
                }else {
                 condition1 = criteriaBuilder.like(root.get("eventTitle"),"%%");
                }
                Predicate condition2 = null;
                if(registerTime!=null) {
                 condition2 = criteriaBuilder.greaterThan(root.get("registerTime"), registerTime);
                }else {
                 condition2 = criteriaBuilder.greaterThan(root.get("registerTime"), new Timestamp(1514736000000L));
                }
                //Predicate conditionX=criteriaBuilder.and(condition1,condition2);
                //query.where(conditionX);
                query.where(condition1,condition2);
             //query.where(getPredicates(condition1,condition2)); //这里可以设置任意条查询条件
                return null;  //这种方式使用JPA的API设置了查询条件,所以不需要再返回查询条件Predicate给Spring Data Jpa,故最后return null
   }
  };
  Page<Event> list=eventRepository.findAll(specification, pageable);
        return ApiReturnUtil.page(list);
 }

ApiReturnUtil.page封装

其实这个大家根据自己的项目自己封装,这部分不作为核心内容,知识之前有部分网友比较纠结这个工具,所以简单放出来参考一下.

 public static ApiReturnObject page(Page returnObject) {
  return new ApiReturnObject(returnObject.getNumber()+"",returnObject.getNumberOfElements()+"",returnObject.getTotalElements()+"",returnObject.getTotalPages()+"","00","success",returnObject.getContent());
 }

ApiReturnObject的主要内容:

 String errorCode="00";
 Object errorMessage;
 Object returnObject;
 String pageNumber;
 String pageSize;
 String totalElements;
 String totalPages;

 public ApiReturnObject(String pageNumber,String pageSize,String totalElements,String totalPages,String errorCode, Object errorMessage, Object returnObject) {
  super();
  this.pageNumber = pageNumber;
  this.errorCode = errorCode;
  this.errorMessage = errorMessage;
  this.returnObject = returnObject;
  this.pageSize = pageSize;
  this.totalElements = totalElements;
  this.totalPages = totalPages;
 }

查询效果

返回对象有用的是pageNumber、pageSize、totalElements、totalPages等属性,可对其进行封装

{
    "errorCode": "00",
    "errorMessage": "success",
    "pageNumber": "1",
    "pageSize": "2",
    "returnObject": [
        {
            "eventTitle": "1111",
            "id": 3,
            "registerTime": 1528702813000,
            "status": "0"
        },
        {
            "eventTitle": "小明失踪",
            "id": 2,
            "registerTime": 1526268436000,
            "status": "0"
        }
    ],
    "totalElements": "5",
    "totalPages": "3"
}

可以查询了。网上关于这个的资料也很少。希望可以帮到大家。

可能遇到的错误

Unable to locate Attribute with the the given name [event] on this ManagedType [org.microservice.tcbj.yytsg.checkcentersys.entity.Event]

出现这样的情况,一般是因为实体类中没有这个属性,例如我Event的是eventTitle,写成了event,就会报错。

JpaSpecificationExecutor接口

20200114补充

JPA 提供动态接口JpaSpecificationExecutor,利用类型检查的方式,利用Specification进行复杂的条件查询,比自己写 SQL 更加便捷和安全.

public interface JpaSpecificationExecutor<T> {
 /**
  * Returns a single entity matching the given {@link Specification}.
  *
  * @param spec
  * @return
  */
 T findOne(Specification<T> spec);
 /**
  * Returns all entities matching the given {@link Specification}.
  *
  * @param spec
  * @return
  */
 List<T> findAll(Specification<T> spec);
 /**
  * Returns a {@link Page} of entities matching the given {@link Specification}.
  *
  * @param spec
  * @param pageable
  * @return
  */
 Page<T> findAll(Specification<T> spec, Pageable pageable);
 /**
  * Returns all entities matching the given {@link Specification} and {@link Sort}.
  *
  * @param spec
  * @param sort
  * @return
  */
 List<T> findAll(Specification<T> spec, Sort sort);
 /**
  * Returns the number of instances that the given {@link Specification} will return.
  *
  * @param spec the {@link Specification} to count instances for
  * @return the number of instances
  */
 long count(Specification<T> spec);
}

Specification

Specification是我们传入进去的查询参数,是一个接口,并且只有一个方法

public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

一个一目了然的方法

第二个实现思路:听说这个方法已经过时了,其实这个方法是最好理解的.这里附上作为思路参考.

public Page<Even> findAll(SearchEven even) {
     Specification<Even> specification = new Specifications<Even>()
         .eq(StringUtils.isNotBlank(even.getId()), "id", even.getId())
         .gt(Objects.nonNull(even.getStatus()), "status", 0)
         .between("registerTime", new Range<>(new Date()-1, new Date()))
         .like("eventTitle", "%"+even.getEventTitle+"%")
         .build();
     return personRepository.findAll(specification, new PageRequest(0, 15));
}

Criteria+TypedQuery

思路三:利用EntityManager相关的CriteriaBuilder +CriteriaQuery+Predicate+TypedQuery进行查询.

@PersistenceContext
private EntityManager em;
/**
 * CriteriaBuilder 安全查询创建工厂,创建CriteriaQuery,创建查询具体具体条件Predicate
 * @author zhengkai.blog.csdn.net
 */
@Override
public List<Even> list(Even even) {
    //查询工厂
	CriteriaBuilder cb = em.getCriteriaBuilder();
    //查询类
	CriteriaQuery<Even> query = cb.createQuery(Even.class);
	//查询条件
	List<Predicate> predicates = new LinkedList<>();
	//查询条件设置
    predicates.add(cb.equal("id", even.getId()));
	predicates.add(cb.like("eventTitle", even.getEventTitle()));
	//拼接where查询
    query.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
    //用JPA 2.0的TypedQuery进行查询
	TypedQuery<Even> typedQuery = em.createQuery(query);
    return typedQuery.getResultList();
}

开发过程中JPA Specification的应用

Specification算是JPA里面比较灵活的查询规范了,方便实现复杂的查询方式。

为什么需要Specification

Spring-Data JPA 本身支持了比较简单的查询方式,也就是根据属性名成结合一些规范编写查询方法,例如,一个Customer对象有name属性,那么如果想要实现根据name来查询,只需要在接口文件中添加一个方法findByName(String name)即可实现。

public interface CustomerRepository extends JpaRepository<Customer, Long> {
  Customer findByName(String name);
  Customer findByEmailAddress(String emailAddress);
  List<Customer> findByLastname(String lastname, Sort sort);
  Page<Customer> findByFirstname(String firstname, Pageable pageable);
}

但是在许多情况下,会有比较复杂的查询,那么这个时候通过自动生成查询方法的方式就不再可行。

应用场景

为了实现复杂查询,JPA提供了Criteria接口,这个是一套标准接口,来看一个例子,在一个平台中,当一个老客户(注册以来两年)生日的时候,系统想要发送一个优惠券给该用户,那么传统使用 JPA 2.0 Criteria API 去实现:

LocalDate today = new LocalDate();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);
query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();
  • 首先获得时间,去比较用户的注册时间
  • 接下来是获得JPA中查询使用的实例
  • 设置查询条件,首先判断今天是否为某个客户的生日,然后判断是否为老客户
  • 执行查询条件,获得满足条件的用户

这里面的主要问题就是代码扩展性比较差,因为需要设置CriteriaBuilder, CriteriaQuery, Root,同时这部分的代码可读性比较差。

JPA Specification实现复杂查询

Specification为了实现可重用的断言,JPA 里面引入了一个Specification接口,接口的封装很简单,如下

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

在Java8中,我们可以非常方便地实现如上使用Criteria实现的效果

public CustomerSpecifications {
    public static Specification<Customer> customerHasBirthday() {
        return (root, query, cb) -> {
            return cb.equal(root.get(Customer_.birthday), today);
        };
    }
    public static Specification<Customer> isLongTermCustomer() {
        return (root, query, cb) -> {
            return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
        };
    }
}

这样对应JPA的repository实现就可以如下

customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());

而其实Specification为我们做的事情就是替我们准备了CriteriaQuery, Root, CriteriaBuilder, 有了这些可重用的断言之后,便可以将他们组合起来实现更加复杂的查询了

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));

JPA多条件、多表查询

如果需要使用Specification,那么对应的Repository需要实现接口JpaSpecificationExecutor

public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {}

单表多条件查询

在结合 Spring Boot 和 JPA 之后,为了四号线多条件查询,并且整理分页, 则可以考虑使用Predicate 断言, 例如现在针对 User , 想要根据用户的不同属性进行模糊查询,同时如果属性值为空或者空字符串,则跳过该属性,不作为查询条件,同时属于单表多条件查询,则

//在spring-jpa 2之后 不再使用 new PageRuest(page, pageSize) 的方式
Pageable pageable = PageRequest.of(page, pageSize);
//实现条件查询,组合查询
Specification<User> specification = new Specification<User>() {
    private static final long serialVersionUID = 1L;
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        String account = request.getAccount();
        String name = request.getName();
        String phone = request.getPhone();
        String accountType = request.getAccountType();
        String city = request.getCity();
        String type = request.getType();
        //用列表装载断言对象
        List<Predicate> predicates = new ArrayList<Predicate>();
        if(org.apache.commons.lang3.StringUtils.isNotBlank(name)) {
            //模糊查询,like
            Predicate predicate = cb.like(root.get("name").as(String.class), "%" + name +"%");
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(account)) {
            Predicate predicate = cb.like(root.get("account").as(String.class), "%" + account +"%");
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(phone)) {
            //精确查询,equal
            Predicate predicate = cb.equal(root.get("phoneNumber").as(String.class), phone);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(accountType)) {
            Predicate predicate = cb.equal(root.get("accountType").as(String.class), accountType);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(city)) {
            Predicate predicate = cb.equal(root.get("city").as(String.class), city);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(type)) {
            Predicate predicate = cb.equal(root.get("type").as(String.class), type);
            predicates.add(predicate);
        }
        //判断是否有断言,如果没有则返回空,不进行条件组合
        if (predicates.size() == 0) {
            return null;
        }
        //转换为数组,组合查询条件
        Predicate[] p = new Predicate[predicates.size()];
        return cb.and(predicates.toArray(p));
    }
};
//交给DAO处理查询任务
Page<User> dataPages = userDAO.findAll(specification, pageable);

多表多条件查询

在许多时候会面对多表多条件查询,实现实例如下

//封装查询对象Specification
Specification<Courier> example = new Specification<Courier>() {
    @Override
    public Predicate toPredicate(Root<Courier> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //获取客户端查询条件
        String company = model.getCompany();
        String courierNum = model.getCourierNum();
        Standard standard = model.getStandard();
        String type = model.getType();
        //定义集合来确定Predicate[] 的长度,因为CriteriaBuilder的or方法需要传入的是断言数组
        List<Predicate> predicates = new ArrayList<>();
        //对客户端查询条件进行判断,并封装Predicate断言对象
        if (StringUtils.isNotBlank(company)) {
            //root.get("company")获取字段名
            //company客户端请求的字段值
            //as(String.class)指定该字段的类型
            Predicate predicate = cb.equal(root.get("company").as(String.class), company);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(courierNum)) {
            Predicate predicate = cb.equal(root.get("courierNum").as(String.class), courierNum);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(type)) {
            Predicate predicate = cb.equal(root.get("type").as(String.class), type);
            predicates.add(predicate);
        }
        //多表的条件查询封装,这是和单表查询的区别
        if (standard != null) {
            if (StringUtils.isNotBlank(standard.getName())) {
                //创建关联对象(需要连接的另外一张表对象)
                //JoinType.INNER内连接(默认)
                //JoinType.LEFT左外连接
                //JoinType.RIGHT右外连接
                Join<Object, Object> join = root.join("standard",JoinType.INNER);
                //join.get("name")连接表字段值
                Predicate predicate = cb.equal(join.get("name").as(String.class), standard.getName());
                predicates.add(predicate);
            }
        }
        //判断结合中是否有数据
        if (predicates.size() == 0) {
            return null;
        }
        //将集合转化为CriteriaBuilder所需要的Predicate[]
        Predicate[] predicateArr = new Predicate[predicates.size()];
        predicateArr = predicates.toArray(predicateArr);
        // 返回所有获取的条件: 条件 or 条件 or 条件 or 条件
        return cb.or(predicateArr);
    }
};
//调用Dao方法进行条件查询
Page<Courier> page = courierDao.findAll(example, pageable);

Spring Data Jpa 简单模糊查询

在一些比较简单的查询条件下,不一定要使用 Specification 接口,比如

@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
  /**
   * username不支持模糊查询,deviceNames支持模糊查询
   * @param deviceNames 模糊查询deviceNames
   * @param username 用户名称
   * @return {@link List<User>}
   */
  List<User> findAllByDeviceNamesContainingAndUsername(String deviceNames,String username);
  /**
   * 其中username不支持模糊查询,deviceNames支持模糊查询
   * 传入的deviceNames需要在前后添加%,否则可能返回的结果是精确查询的结果
   * @param deviceNames 模糊查询deviceNames
   * @param username 用户名称
   * @return {@link List<User>}
   */
  List<User> findAllByDeviceNamesLikeAndUsername(String deviceNames,String username);
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JPA的多表复杂查询的方法示例

    最近工作中由于要求只能用hibernate+jpa 与数据库进行交互,在简单查询中,jpa继承CrudRepository接口 ,然后利用jpa的方法命名规范进行jpql查询,然而在进行复杂查询时,需要继承JpaSpecificationExecutor接口 利用Specification 进行复杂查询,由于我自己就遇到了这一问题,查了好多资料,虽然有方法,但是都没有一个详细的讲解,以至于知道方法而不能很好的利用jpa复杂查询的方便之处.我将举几个栗子,来详细的说一下我自己在使用jpa多表复杂查

  • Spring data jpa的使用与详解(复杂动态查询及分页,排序)

    一. 使用Specification实现复杂查询 (1) 什么是Specification Specification是springDateJpa中的一个接口,他是用于当jpa的一些基本CRUD操作的扩展,可以把他理解成一个spring jpa的复杂查询接口.其次我们需要了解Criteria 查询,这是是一种类型安全和更面向对象的查询.而Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor. 而JpaSpecifica

  • Spring Data JPA 复杂/多条件组合分页查询

    话不多说,请看代码: public Map<String, Object> getWeeklyBySearch(final Map<String, String> serArgs, String pageNum, String pageSize) throws Exception { // TODO Auto-generated method stub Map<String,Object> resultMap=new HashMap<String, Object&

  • SpringDataJPA之Specification复杂查询实战

    目录 SpringDataJPA Specification复杂查询 前言 实现 Specification与Controller业务逻辑 ApiReturnUtil.page封装 查询效果 可能遇到的错误 JpaSpecificationExecutor接口 Specification 一个一目了然的方法 Criteria+TypedQuery 开发过程中JPA Specification的应用 为什么需要Specification 应用场景 JPA Specification实现复杂查询 J

  • JPA Specification常用查询+排序实例

    目录 JPA Specification常用查询+排序 1.第一步:继承父类 2.第二步 JPA Specification复杂查询+排序 需求 开始了 一.dao 二.service 三.排序 前端 结束语 JPA Specification常用查询+排序 1.第一步:继承父类 public interface TblCarton2RCardLogRepository extends JpaRepository<TblCarton2RCardLog, String>,JpaSpecifica

  • MySQL派生表联表查询实战过程

    目录 前情提要: 查询过程: 总结: 前情提要: 公司运营的一个商城系统,忽然发现订单提现功能有问题,有大量的商户体现金额和订单金额不一致.于是产生了需求,需要把提现表和供应商表作为一个结果集,连接上订单表中的订单金额,通过计算订单表的金额和体现表商户提现的金额进行比对,查看商户是多提现了还是少提现了. 下面记录我的查询过程. 查询过程: 刚开始,第一步我以提现表为主表,查询出来相关结果.MySQL语句如下 SELECT count(ysw.supply_id) AS '提现次数',ysw.us

  • 隐式转换引起的sql慢查询实战记录

    引言 实在很无语呀,遇到一个mysql隐式转换问题,问了周边的dba大拿该问题,他们居然反问我,你连这个也不知道?白白跟他们混了那么长   尼玛,我还真不知道.罪过罪过-. 问题是这样的,一个字段叫task_id, 本身是varchar字符串类型,但是因为老系统时间太长了,我以为是int或者bigint,所以直接在代码写sql跑数据,结果等了好久就是没有反应,感觉要坏事呀.在mysql processlist里看到了该sql语句,直接kill掉. 该字段是有索引的,并且他的sql选择性很高,索引

  • C#中的DataTable查询实战教程

    DataTable查询 工作中遇到了需要进行DataTable进行查询的需求,简单研究了一下,最终使用一下方案实现,简单记录一下便于以后使用. DataTable dt = dataBox.GetDataForDataTable();//获取DataTable所有数据,准备进行查询 DataRow[] dtRow = dt.Select("调剂日期='"+MediumCode.Text.Trim()+"'");//根据查询条件,筛选出所有满足条件的列 DataTab

  • NumPy对数组按索引查询实战方法总结

    目录 前期准备及前情回顾 基础索引 一维数组 二维数组 神奇索引 一维数组 二维数组0 布尔索引(常用) 一维数据 二维数组 布尔索引条件的组合 总结 前期准备及前情回顾 #对于一维向量用np.arange生成以元组形式输出从0开始的数组([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) #对于二维向量(及多维向量),用np.arange生成以元组形式输出从0开始到20结束的数组,用np.reshape(4, 5)函数把一维向量转换为4行5列的二维向量 理解:numpy的二维向量对

  • MySQL多表链接查询核心优化

    概述 在一般的项目开发中,对数据表的多表查询是必不可少的.而对于存在大量数据量的情况时(例如百万级数据量),我们就需要从数据库的各个方面来进行优化,本文就先从多表查询开始.其他优化操作,后续另外更新,敬请关注. 数据背景 现假设有一个中学学校,学校中的年级有一年级.二年级.三年级,每个年级有两个班级.分别为101.102.201.202.301.302. 现在我们要为这个学校建立一个考试成绩统计系统.为此,我们对数据库的设计画了如下ER图: 根据ER图,我们设计了数据表,结构如下: class

  • EasyUi+Spring Data 实现按条件分页查询的实例代码

    Spring data 介绍 Spring data 出现目的 为了简化.统一 持久层 各种实现技术 API ,所以 spring data 提供一套标准 API 和 不同持久层整合技术实现 . 自己开发 Repository 只需要继承 JpaRepository 接口CrudRepository save. delete. deteleAll. findAll. findOne. count PagingAndSortingRepository findAll(Sort) 基于排序的查询.

  • SpringBoot如何整合SpringDataJPA

    这篇文章主要介绍了SpringBoot整合SpringDataJPA代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.pom.xml添加依赖 <dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-we

  • mybatis Example Criteria like 模糊查询问题

    目录 简介 Criteria类 简单实例 生成简单的WHERE子句 复杂查询 模糊查询实战 总结 用Mybatis代码生成工具会产生很多个XXXExample类,这些类的作用是什么? 查阅了很多资料,在这里总结归纳一下 简介 XXXExample类用于构造复杂的筛选条件 它包含一个名为Criteria的内部静态类,它包含将在where子句中一起结合的条件列表. Criteria类的集合允许您生成几乎无限类型的where子句. 可以使用createCriteria方法或or方法创建Criteria

随机推荐