springDataJpa入门教程(2)-Specification动态条件查询+排序+分页

springDataJpa入门教程

springDataJpa入门教程(2)-Specification动态条件查询+排序+分页

上一篇,讲解了springDataJpa的基本增删改查操作,下面接着上一篇的内容讲解使用springDataJpa来实现复杂查询。通常,由前端传递到后端的查询条件是可变的,这就要求查询的sql语句也是动态可变的,很多情况下数据量是很大的,需要用分页展示数据,甚至还要求查询的结果按照某个属性排序。下面来讲解使用Specification来实现复杂查询。由于本人水平有限,教程中难免出现错误,敬请谅解,欢迎大家批评指正。源码地址:源码下载地址。java学习交流群:184998348,欢迎大家一起交流学习。

Specification动态条件查询+排序+分页

下面是这一节会用到的实体类 User类以及UserDTO类.
1). User

package com.thizgroup.jpa.study.model;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
//这里User类对应的表是tb_user
@Table(name = "tb_user")
@Data//使用lombok生成getter、setter
@NoArgsConstructor//使用lombok生成无参构造方法
public class User {

  @Id
  //指定id生成策略为自增
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

	//定义属性名对应的数据库表字段名称
  @Column(name = "name",columnDefinition = "varchar(64)")
  private String name;

  @Column(name = "mobile",columnDefinition = "varchar(64)")
  private String mobile;

  @Column(name = "email",columnDefinition = "varchar(64)")
  private String email;

  @Column(name = "age",columnDefinition = "smallint(64)")
  private Integer age;

  @Column(name = "birthday",columnDefinition = "timestamp")
  private Date birthday;

  //地址
  @Column(name = "address_id",columnDefinition = "bigint(20)")
  private Long addressId;

  @Column(name = "create_date",columnDefinition = "timestamp")
  private Date createDate;

  @Column(name = "modify_date",columnDefinition = "timestamp")
  private Date modifyDate;

  @Builder(toBuilder = true)//Builder注解可以实现链式编写,后面会用过
  public User(Long id,String name, String mobile, String email, Integer age, Date birthday,
      Long addressId) {
    this.id = id;
    this.name = name;
    this.mobile = mobile;
    this.email = email;
    this.age = age;
    this.birthday = birthday;
    this.addressId = addressId;
  }
}

2). UserDTO

package com.thizgroup.jpa.study.dto;

import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data//使用lombok生成getter、setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDTO {

  private Long id;

  private String name;//姓名

  private Integer age;//年龄

  private String mobile;//手机号

  private String email;//邮箱

  private Date birthday;//生日

  private Date createDate;//创建时间

  private Date modifyDate;//修改时间

  private Date startTime;//开始时间

  private Date endTime;//结束时间
}

1.需求:根据动态查询条件查询用户分页数据,并按照按照创建时间倒序排序,动态查询条件:用户名、手机号、邮箱模糊查询,年龄精确查询,生日按照时间段查询。

首先,在IUserService接口中添加一个方法,

package com.thizgroup.jpa.study.service;

import com.thizgroup.jpa.study.dto.PageRecord;
import com.thizgroup.jpa.study.dto.UserDTO;
import com.thizgroup.jpa.study.dto.UserProjection;
import com.thizgroup.jpa.study.model.User;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

/**
 * 用户服务
 */
public interface IUserService {

  /**
   * 查询用户分页信息
   * @param userDTO
   * @return
   */
  Page<User> findUserListByPage(UserDTO userDTO, Pageable pageable);
}

下面在UserServiceImpl实现类中实现这个方法,

package com.thizgroup.jpa.study.service.impl;

import com.thizgroup.jpa.study.dao.UserRepository;
import com.thizgroup.jpa.study.dto.UserDTO;
import com.thizgroup.jpa.study.model.User;
import com.thizgroup.jpa.study.service.IUserService;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

@Service
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
public class UserServiceImpl implements IUserService {

  @Autowired
  private UserRepository userRepository;

  @Override
  //Specification 实现单表多条件分页查询及排序
  public Page<User> findUserListByPage(UserDTO userDTO, Pageable pageable) {

    Specification<User> specification = new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery,
          CriteriaBuilder criteriaBuilder) {

        List<Predicate> andList = new ArrayList<>();//用来封装and条件
        List<Predicate> orList = new ArrayList<>();//用来封装or条件

       if(userDTO  != null){
         if(StringUtils.isNotBlank(userDTO.getName())){
           //模糊查询
           Predicate predicate = criteriaBuilder.like(root.get("name"), "%" + userDTO.getName() + "%");
           andList.add(predicate);
         }
         if(null != userDTO.getAge()){
           //精确查询
           Predicate predicate = criteriaBuilder.equal(root.get("age"), userDTO.getAge());
           andList.add(predicate);
         }
         //求生日在某个时间段范围内的用户
         if(null != userDTO.getStartTime()){
           //大于等于
           Predicate predicate = criteriaBuilder
               .greaterThanOrEqualTo(root.get("birthday"), userDTO.getStartTime());
           andList.add(predicate);
         }
         if(null != userDTO.getEndTime()){
           //小于等于
           Predicate predicate = criteriaBuilder
               .lessThanOrEqualTo(root.get("birthday"), userDTO.getEndTime());
           andList.add(predicate);
         }

         if(StringUtils.isNotBlank(userDTO.getMobile())){
           Predicate predicate = criteriaBuilder.like(root.get("mobile"), "%"+userDTO.getMobile()+"%");
           orList.add(predicate);
         }
         if(StringUtils.isNotBlank(userDTO.getEmail())){
           Predicate predicate = criteriaBuilder.like(root.get("email"), "%"+userDTO.getEmail()+"%");
           orList.add(predicate);
         }

       }

        Predicate andPredicate = null;
        Predicate orPredicate = null;

       //拼接and条件
        if(andList.size()>0){
          //转换为数组
          Predicate[] predicates = andList.toArray(new Predicate[]{});
          andPredicate = criteriaBuilder.and(predicates);
        }

        //拼接or条件
        if(orList.size()>0){
          //转换为数组
          Predicate[] predicates = orList.toArray(new Predicate[]{});
          orPredicate = criteriaBuilder.or(predicates);
        }

        //拼接查询条件
        List<Predicate> predicates = new ArrayList<>();
        if(andPredicate != null) predicates.add(andPredicate);
        if(orPredicate != null) predicates.add(orPredicate);

        Predicate predicate = null;
        if(predicates.size()>0){
          //转换为数组
          Predicate[] predicateArray = predicates.toArray(new Predicate[]{});
          predicate = criteriaBuilder.and(predicateArray);
        }

        List<Order> orderList = new ArrayList<>();//封装排序条件
        //按照创建时间 倒序排序
        orderList.add(criteriaBuilder.desc(root.get("createDate")));
        // 按照生日顺序排序
        //orderList.add(criteriaBuilder.asc(root.get("birthday")));

        //设置排序条件
        criteriaQuery.orderBy(orderList);

        //返回查询条件
        return predicate;
      }
    };

    return userRepository.findAll(specification,pageable);
  }

其中, userRepositoryPage findAll( Specification specification, Pageable pageable );是由JPA提供的方法,我们只需要提供查询条件以及分页参数即可,这里强调一下,JPA的分页是从0开始的,0表示第一页。

接下来,写一个单元测试来验证上述代码,

package com.thizgroup.jpa.study.service;

import com.thizgroup.jpa.study.JpaApplication;
import com.thizgroup.jpa.study.dto.UserDTO;
import com.thizgroup.jpa.study.model.User;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest(classes={JpaApplication.class})
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
public class UserServiceImplTest {

  @Autowired
  private IUserService userService;

  private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  @Test
  public void findUserListByPageTest() throws Exception{

    UserDTO userDTO = new UserDTO();
    //userDTO.setName("张");
    //userDTO.setAge(50);
    //userDTO.setStartTime(dateFormat.parse("2001-09-16 08:00:00"));
    //userDTO.setEndTime(dateFormat.parse("2008-09-15 08:00:00"));
    userDTO.setMobile("158989");
    userDTO.setEmail("hu");
    //注意:jpa的分页是从0开始的
    Page<User> pageList = userService.findUserListByPage(userDTO, PageRequest.of(0, 15));
    System.out.println("分页信息:");
    System.out.println("总记录数:"+pageList.getTotalElements()+",总页数:"+pageList.getTotalPages());
    System.out.println("页码:"+(pageList.getNumber()+1)+",每页条数:"+pageList.getSize());
    List<User> content = pageList.getContent();
    content = null == content? new ArrayList<>() : content;
    content.forEach(user->System.out.println(user));
  }
}

执行一下单元测试,结果如下:

分页信息:
总记录数:2,总页数:1
页码:1,每页条数:15
User(id=3, name=诸葛亮, mobile=158989989, email=zhu@qq.com, age=54, birthday=2001-09-16 08:00:00.0, addressId=22, createDate=2019-09-06 05:50:01.0, modifyDate=2019-08-08 05:46:17.0)
User(id=1, name=张三, mobile=156989989, email=hu@qq.com, age=35, birthday=2008-09-16 08:00:00.0, addressId=11, createDate=2019-08-06 05:50:01.0, modifyDate=2019-08-08 05:46:11.0)

至此,springDataJpa实现Specification动态条件查询+排序+分页就介绍完了,有需要源码的朋友,请到git上下载源码,源码地址:源码下载地址。java学习交流群:184998348,欢迎大家一起交流学习。

上一篇:springDataJpa入门教程(1)-基于springBoot的基本增删改查
下一篇:springDataJpa入门教程(3)-基于EntityManager原生sql多表联合查询+动态条件查询+分页
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用Spring Data JPA进行多条件组合条件查询时,可以通过使用Specification来实现。Specification是一个接口,我们可以自定义一个实现该接口的类,然后在查询方法中传入该Specification对象来指定查询条件。 首先,我们需要在仓库接口中定义一个方法,该方法接收一个Specification参数,并返回查询结果。如下所示: ```java @Repository public interface UserRepository extends JpaRepository<User, Long> { List<User> findAll(Specification<User> spec); } ``` 接下来,我们在Specification实现类中重写toPredicate方法,在该方法中使用CriteriaBuilder构建查询条件,并返回一个Predicate对象表示查询条件。例如,我们可以按用户名和年龄进行查询,如下所示: ```java public class UserSpecification implements Specification<User> { private String username; private int age; public UserSpecification(String username, int age) { this.username = username; this.age = age; } @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { List<Predicate> predicates = new ArrayList<>(); if (username != null) { predicates.add(criteriaBuilder.equal(root.get("username"), username)); } if (age != 0) { predicates.add(criteriaBuilder.equal(root.get("age"), age)); } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); } } ``` 最后,我们可以在服务类中调用仓库方法并传入自定义的Specification对象来进行查询。例如: ```java @Service public class UserService { @Autowired private UserRepository userRepository; public List<User> getUsers(String username, int age) { UserSpecification spec = new UserSpecification(username, age); return userRepository.findAll(spec); } } ``` 这样,我们就可以根据传入的条件来进行组合条件查询了。当传入的条件为空时,不会加入到查询中。当传入的条件有值时,则会根据该条件进行查询。这样,就实现了多条件组合条件查询

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值