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);
}
其中, userRepository的 Page 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,歡迎大家一起交流學習。