springDataJpa入門教程
-
springDataJpa入門教程(1)-基於springBoot的基本增刪改查
-
springDataJpa入門教程(2)-Specification動態條件查詢+排序+分頁
-
springDataJpa入門教程(3-1)-基於EntityManager原生sql多表聯合查詢+動態條件查詢+分頁
-
springDataJpa入門教程(3-2)-基於EntityManager原生sql多表聯合查詢+動態條件查詢+分頁返回自定義實體類對象
-
springDataJpa入門教程(4)-Example單表動態條件查詢+分頁
-
springDataJpa入門教程(5)-單表動態條件查詢+分頁
-
springDataJpa入門教程(6)-多表動態條件查詢+分頁
-
springDataJpa入門教程(7)-基於springDataJpa投影(Projection)返回自定義實體類對象
-
springDataJpa入門教程(8)-JPA EnableJpaAuditing 審計功能
-
springDataJpa入門教程(9)-spring jpa實體屬性類型轉換器AttributeConverter的用法
-
springDataJpa入門教程(10)-JPA使用過程中遇到的坑及解決方法
springDataJpa入門教程(3-1)-基於EntityManager原生sql多表聯合查詢+動態條件查詢+分頁
在JPA實際應用中,很多場景下需要同時對多個表進行操作,這時候,JPA提供的JPQL語句就無法滿足實際應用的需求,需要用到原生sql來實現查詢,簡單的原生sql查詢直接使用@Query註解就可以完成,但是要想實現多表並且動態條件查詢,則@Query註解也顯得力不從心,這時候EntityManager便派上用場了。
下面接着上一節的內容來講解如何使用EntityManager實現多表聯合查詢+動態條件查詢+分頁。有需要源碼的朋友,請到git上下載源碼,源碼地址:源碼下載地址。java學習交流羣:184998348,歡迎大家一起交流學習。
- 實體類分別是User類、Address類,下面這兩個類的代碼:
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
@Table(name = "tb_user")
@Data//使用lombok生成getter、setter
@NoArgsConstructor//生成無參構造方法
public class User {
@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)
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;
}
}
package com.thizgroup.jpa.study.model;
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.Data;
@Entity
@Table(name="tb_address")
@Data//使用lombok生成getter、setter
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "country",columnDefinition = "varchar(64)")
private String country;
@Column(name = "province",columnDefinition = "varchar(64)")
private String province;
@Column(name = "city",columnDefinition = "varchar(64)")
private String city;
}
- 涉及到DTO及輔助類
package com.thizgroup.jpa.study.dto;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.Getter;
import org.springframework.util.Assert;
@Data//使用lombok生成getter、setter
public class PageBean implements Serializable {
@Getter
private long totalCount;//總記錄數
@Getter
private int totalPages;//總頁數
@Getter
private int pageNumber;//第幾頁
@Getter
private int pageSize;//每頁條數
public PageBean(int pageNumber, int pageSize, long totalCount) {
pageNumber = pageNumber <0 ? 0 :pageNumber;//jpa中頁碼從0開始
pageSize = pageSize <=0 ? 15 : pageSize;//默認取15條記錄
//計算總頁數
int totalPages = (int)((totalCount+pageSize-1)/pageSize);
//計算起始頁
if(totalPages>0){
pageNumber = totalPages <= pageNumber ? totalPages-1 : pageNumber;
}
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalPages = totalPages;
this.totalCount = totalCount;
}
public PageBean(PageBean pageBean) {
Assert.notNull(pageBean,"pagebean cannot be null");
this.pageNumber = pageBean.getPageNumber();
this.pageSize = pageBean.getPageSize();
this.totalPages = pageBean.getTotalPages();
this.totalCount = pageBean.getTotalCount();
}
}
package com.thizgroup.jpa.study.dto;
import java.util.List;
import lombok.Data;
import lombok.Getter;
@Data
public class PageRecord<T> extends PageBean {
@Getter
private List<T> data;//數據列表
public PageRecord(int pageNumber, int pageSize, long totalCount,List<T> data) {
super(pageNumber, pageSize, totalCount);
this.data = data;
}
public PageRecord(PageBean pageBean, List<T> data){
super(pageBean);
this.data =data;
}
}
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 AddressDTO addressDTO;//地址
private Date createDate;//創建時間
private Date modifyDate;//修改時間
private Date startTime;//開始時間
private Date endTime;//結束時間
}
package com.thizgroup.jpa.study.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data//使用lombok生成getter、setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddressDTO {
private Long id;
private String country;//國家
private String province;//省份
private String city;//城市
}
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.model.User;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* 用戶服務
*/
public interface IUserService {
/**
* 查詢用戶分頁信息
* @param userDTO
* @return
*/
PageRecord<UserDTO> findUserDTOListByPage(UserDTO userDTO, Pageable pageable);
}
然後在UserServiceImpl中實現該方法,
package com.thizgroup.jpa.study.service.impl;
import com.thizgroup.jpa.study.dao.UserDao;
import com.thizgroup.jpa.study.dto.AddressDTO;
import com.thizgroup.jpa.study.dto.PageRecord;
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.stream.Collectors;
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.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 UserDao userDao;
@Override
public PageRecord<UserDTO> findUserDTOListByPage(UserDTO userDTO, Pageable pageable) {
return userDao.findUserListByPage(userDTO,pageable);
}
主要的代碼邏輯在dao層,讓我們來看下UserDao 的代碼,
package com.thizgroup.jpa.study.dao;
import com.thizgroup.jpa.study.dto.AddressDTO;
import com.thizgroup.jpa.study.dto.PageBean;
import com.thizgroup.jpa.study.dto.PageRecord;
import com.thizgroup.jpa.study.dto.UserDTO;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
/**
* 用戶服務
*/
@Repository
public class UserDao {
@Autowired
private EntityManager entityManager;
//使用entityManager實現多表聯合帶條件帶分頁查詢及排序
public PageRecord<UserDTO> findUserListByPage(UserDTO userDTO, Pageable pageable){
List<Object> args = new ArrayList<>();//用於封裝參數
StringBuffer sql = new StringBuffer();
sql.append(
" select u.id,u.name,u.age,u.birthday,u.mobile,u.email,a.country,a.province,a.city,"
+ " u.create_date "
+ " from tb_user u "
+ " left join tb_address a on u.address_id = a.id "
+ " where 1=1 "
);
if(userDTO != null){
if(StringUtils.isNotBlank(userDTO.getName())){
//模糊查詢
sql.append(" and u.name like ? ");
args.add("%"+userDTO.getName()+"%");
}
if(null != userDTO.getAge()){
//精確查詢
sql.append(" and u.age = ? ");
args.add(userDTO.getAge());
}
//求生日在某個時間段範圍內的用戶
if(null != userDTO.getStartTime()){
//大於等於
sql.append(" and u.birthday >= ? ");
args.add(userDTO.getStartTime());
}
if(null != userDTO.getEndTime()){
//小於等於
sql.append(" and u.birthday <= ? ");
args.add(userDTO.getEndTime());
}
if(userDTO.getAddressDTO() != null) {
AddressDTO addressDTO = userDTO.getAddressDTO();
//查詢某個城市的用戶
if(StringUtils.isNotBlank(addressDTO.getCity())) {
sql.append(" and a.city = ? ");
args.add(addressDTO.getCity());
}
}
}
//按照創建時間倒序排序
sql.append(" order by u.create_date desc ");
//創建query對象
Query query = entityManager.createNativeQuery(sql.toString());
//設置查詢參數
if(args.size()>0){
for(int i=0;i<args.size();i++){
//注意:jpa的setParameter是從1開始的
query.setParameter(i+1,args.get(i));
}
}
PageBean pageBean = findPageBean(sql.toString(), args, pageable);
//分頁查詢
query.setFirstResult(pageBean.getPageNumber()*pageBean.getPageSize());
query.setMaxResults(pageBean.getPageNumber()*pageBean.getPageSize()+pageBean.getPageSize());
List<Object[]> resultList = query.getResultList();
//封裝查詢結果
List<UserDTO> userDTOList = new ArrayList<>();
if(resultList != null && resultList.size() > 0) {
resultList.forEach(objs -> {
UserDTO userDTONew = UserDTO.builder()
.id(((BigInteger)objs[0]).longValue())
.name((String) objs[1])
.age(((Short) objs[2]).intValue())
.birthday((Date) objs[3])
.mobile((String) objs[4])
.email((String) objs[5])
.addressDTO(
AddressDTO.builder()
.country((String) objs[6])
.province((String) objs[7])
.city((String) objs[8])
.build()
)
.createDate((Date) objs[9])
.build();
//添加到列表中
userDTOList.add(userDTONew);
});
}
return new PageRecord<UserDTO>(pageBean,userDTOList);
}
//查詢分頁信息
PageBean findPageBean(String sql,List<Object> args,Pageable pageRequest){
String countSql = " select count(*) from (" + sql + ") tb ";
Query countQuery = entityManager.createNativeQuery(countSql);
if(args != null && args.size()>0){
for(int i=0;i<args.size();i++){
countQuery.setParameter(i+1,args.get(i));
}
}
//查詢總記錄數
long totalCount = ((BigInteger)countQuery.getSingleResult()).longValue();
return new PageBean(pageRequest.getPageNumber(),pageRequest.getPageSize(),totalCount);
}
}
說大體思路,首先使用StringBuffer 來動態拼接查詢條件,使用List來封裝所有的查詢參數,根據sql語句查詢總記錄數,然後計算出分頁信息,再通過setFirstResult和setMaxResults設置查詢記錄的起始位置,最後通過循環將查詢的結果集封裝到對象中。
下面寫個單元測試驗證一下,
package com.thizgroup.jpa.study.service;
import com.thizgroup.jpa.study.JpaApplication;
import com.thizgroup.jpa.study.dto.AddressDTO;
import com.thizgroup.jpa.study.dto.PageRecord;
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 {
@Test
public void findUserListByPage2Test() throws Exception {
UserDTO userDTO = UserDTO.builder()
.name("諸")
//.age(35)
//.startTime(dateFormat.parse("2001-09-16 08:00:00"))
//.endTime(dateFormat.parse("2008-09-16 07:00:00"))
.addressDTO(AddressDTO.builder()
.city("武漢")
.build())
.build();
//注意:jpa的分頁是從0開始的
PageRecord<UserDTO> userListByPage2 = userService
.findUserDTOListByPage(userDTO, PageRequest.of(0, 15));
System.out.println("分頁信息:");
System.out.println("總記錄數:"+userListByPage2.getTotalCount()+",總頁數:"+userListByPage2.getTotalPages());
System.out.println("頁碼:"+(userListByPage2.getPageNumber()+1)+",每頁條數:"+userListByPage2.getPageSize());
List<UserDTO> content = userListByPage2.getData();
content = null == content? new ArrayList<>() : content;
content.forEach(user->System.out.println(user));
}
}
運行一下單元測試,結果如下:
分頁信息:
總記錄數:1,總頁數:1
頁碼:1,每頁條數:15
UserDTO(id=3, name=諸葛亮, age=54, mobile=158989989, email=zhu@qq.com, birthday=2001-09-16 08:00:00.0, addressDTO=AddressDTO(id=null, country=中國, province=湖北, city=武漢), createDate=2019-09-06 05:50:01.0, modifyDate=null, startTime=null, endTime=null)
至此,springDataJpa基於EntityManager原生sql多表聯合查詢+動態條件查詢+分頁就介紹完了,有需要源碼的朋友,請到git上下載源碼,源碼地址:https://github.com/hgq0916/springdatajpa-study.git。java學習交流羣:184998348,歡迎大家一起交流學習。