資料庫單表查詢,list分頁關聯展示
應用背景
一般我們在開發資料量不是很大的中小型企業系統來說,直接使用SQL關聯,多表聯合查詢就可以了,因為這樣在專案開發過程中非常的高效。但是一旦在遇到大資料量的背景前提下,原始的關聯查詢方式逐漸的顯現出了越來越多的弊端。
我們引入海爾電商技術總監Richie的觀點來說,主要有以下幾點:
從邏輯架構分層原則來看
關聯關係代表了業務規則/邏輯,毫無約束大量使用關聯查詢,就是把大量的業務規則和邏輯放在資料庫來執行了,資料庫消耗cpu、記憶體、io等資源進行關聯操作,實際上是在做應用該做的事情。
從資源利用率方面看
大部分場景下,並不是所有關聯查詢的結果都被有效使用了。例如後臺管理的列表介面,通常都會分頁顯示,關聯查詢的結果集,只有當前頁的資料被使用,其他都是無用的,但資料庫需要消耗額外資源得到全部結果集,再從中得到當前頁資料。
從架構的伸縮性方面看
大量的關聯查詢會導致集中式的資料庫架構很難向分散式架構轉換,伸縮性方面的優化難度高。
總結:
關聯查詢方便快速,開發效率比較好,如果系統、資料庫經過一些垂直優化手段完全能夠滿足效能要求是可以使用的,例如中小企業的內部管理系統等。
不使用關聯查詢在架構層面有很多優點,但對系統分析和設計、開發能力要求高。一般在網際網路行業等使用者數較多的情況下最好重視這方面。
理論上不存在什麼複雜場景,如果不使用資料庫的關聯查詢就無法滿足需求的。巨無霸的ERP系統SAP,基本整個系統功能都是用單表查詢實現的。
程式碼實現
從前面的介紹中我們瞭解到,很多高效能的應用都會對關聯查詢進行分解。簡單的來說,可以對每一個表進行一次單表查詢,然後將結果在應用程式中進行關聯。
比如說下面截圖中的資料就是利用這種原理實現的。
下面我以實際的程式碼為例來進行說明,其中由於資料均為模擬資料,所以說我直接通過手寫的方式代替資料庫查詢來給結果集進行賦值。
join()
實現多表關聯filter()
實現where查詢listPage()
實現資料分頁功能。
實體類
Class
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class Class {
private String id;
private String name;
}
Student
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class Student {
private String id;
private String name;
private String classId;
}
PageQuery
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@ApiModel(value = "PageQuery",description = "這個是分頁查詢實體類",subTypes = PageQuery.class)
public class PageQuery<T> {
//頁數
@ApiModelProperty(name = "page",value = "頁數索引",notes = "請求的頁數",required = true)
private int page;
//每頁顯示的資訊數
@ApiModelProperty(name = "limit",value = "資訊條數",notes = "每頁展示的資訊條數",required = true)
private int limit;
//開始座標
@ApiModelProperty(name = "start",value = "sql查詢起始頁",notes = "開始座標")
private int start;
//截止座標
@ApiModelProperty(name = "end",value = "list擷取截止座標",notes = "截止座標")
private int end;
public PageQuery(int page, int limit){
this.start = (page - 1) * limit;
this.limit = limit;
this.end = this.start + this.limit;
}
/**
* list陣列分頁
* @param list
* @return
*/
public List<T> getListPage(List<T> list){
List<T> subedList = null;
//如果list陣列長度大於開始座標,則有效,否則返回空陣列。
if(list.size() >= this.start){
//如果list陣列的長度小於等於截止座標,則list索引值有可能會越界,故此時的最終list索引值應該修正為陣列的長度
if(list.size() <= end){
subedList = list.subList(this.start,list.size());
} else { //如果list陣列的長度大於截止座標,則list索引值始終有效,此時最終list索引值應該為截止座標
subedList = list.subList(this.start,this.end);
}
//返回擷取的陣列
return subedList;
}
return Lists.newArrayList();
}
}
ClassStudentDTO
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
public class ClassStudentDTO {
private String id;
private String studentId;
private String classId;
private String className;
private String studentName;
}
測試程式碼
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.mysql.cj.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.Map;
@Slf4j
public class ListPageTest {
private List<Class> classList = Lists.newArrayList();
private List<Student> studentList = Lists.newArrayList();
/**
* 初始化班級,學生資訊
*/
@Before
public void initDB(){
for(int i = 1;i <= 3;i ++){
Class clazz = Class.builder()
.id(StringUtils.getUniqueSavepointId())
.name("班級" + i)
.build();
classList.add(clazz);
for(int j = 1;j <= 3;j ++){
Student student = Student.builder()
.id(StringUtils.getUniqueSavepointId())
.name("學生" + i + j)
.classId(clazz.getId())
.build();
studentList.add(student);
}
}
}
/**
* 測試分頁
*/
@Test
public void test(){
//連線表
List<ClassStudentDTO> joinedTable = join(classList,studentList);
//假設傳入的查詢條件是“班級2”
//查詢目標班級資料
List<Class> searchedClasses = getSearchedClass("班級2");
//過濾表
List<ClassStudentDTO> searchedClassStudentDTO = filter(joinedTable,searchedClasses);
//假設前臺傳入的查詢分頁資訊為:第二頁,每頁顯示2條資料
PageQuery pageQuery = new PageQuery(1,2);
//list分頁
List<ClassStudentDTO> pagedList = listPage(searchedClassStudentDTO,pageQuery);
log.info("資訊總條數為:{}",searchedClassStudentDTO.size());
log.info("查詢的內容:");
//查詢的內容
pagedList.forEach(classStudentDTO -> log.info(classStudentDTO.toString()));
}
/**
* 連線表
* @param classList
* @param studentList
* @return
*/
private List<ClassStudentDTO> join(List<Class> classList,List<Student> studentList){
Map<String,Class> classMap = Maps.newHashMap();
classList.forEach(clazz -> {
classMap.put(clazz.getId(),clazz);
});
List<ClassStudentDTO> classStudentDTOS = Lists.newArrayList();
studentList.forEach(student -> {
ClassStudentDTO classStudentDTO = ClassStudentDTO.builder()
.id(StringUtils.getUniqueSavepointId())
.studentId(student.getId())
.classId(student.getClassId())
.className(classMap.get(student.getClassId()).getName())
.studentName(student.getName())
.build();
classStudentDTOS.add(classStudentDTO);
});
return classStudentDTOS;
}
/**
* 查詢目標班級資料
* @param className
* @return
*/
private List<Class> getSearchedClass(String className){
List<Class> searchedClasses = Lists.newArrayList();
classList.forEach(clazz -> {
if(clazz.getName().equals(className)){
searchedClasses.add(clazz);
}
});
return searchedClasses;
}
/**
* 過濾資料
* @param joinedTable
* @param searchedClasses
* @return
*/
private List<ClassStudentDTO> filter(List<ClassStudentDTO> joinedTable,List<Class> searchedClasses){
List<ClassStudentDTO> searchedClassStudentDTO = Lists.newArrayList();
Multimap<String,ClassStudentDTO> multimap = ArrayListMultimap.create();
joinedTable.forEach(classStudentDTO -> {
multimap.put(classStudentDTO.getClassId(),classStudentDTO);
});
searchedClasses.forEach(searchedClass -> {
List<ClassStudentDTO> dtos = (List<ClassStudentDTO>) multimap.get(searchedClass.getId());
searchedClassStudentDTO.addAll(dtos);
});
return searchedClassStudentDTO;
}
/**
* list分頁
* @param searchedClassStudentDTO
* @param pageQuery
* @return
*/
private List<ClassStudentDTO> listPage(List<ClassStudentDTO> searchedClassStudentDTO,PageQuery pageQuery){
return pageQuery.getListPage(searchedClassStudentDTO);
}
}
測試結果
[13:05:32.571] INFO com.baishun.login.listPage.ListPageTest 60 test - 資訊總條數為:3
[13:05:32.581] INFO com.baishun.login.listPage.ListPageTest 61 test - 查詢的內容:
[13:05:32.582] INFO com.baishun.login.listPage.ListPageTest 63 lambda$test$0 - ClassStudentDTO(id=cff92128_902a_471f_ad67_d75427d17006, studentId=6a673faa_0e62_4081_9cb4_1719489cfce6, classId=acc94722_e24a_4a11_b632_d23d9c5eddc8, className=班級2, studentName=學生21)
[13:05:32.583] INFO com.baishun.login.listPage.ListPageTest 63 lambda$test$0 - ClassStudentDTO(id=93600c44_2dd1_49c1_b903_1fb585822edd, studentId=d41caa65_dae2_48b6_b6e8_166f498a7ae3, classId=acc94722_e24a_4a11_b632_d23d9c5eddc8, className=班級2, studentName=學生22)