因爲篇幅限制,本文中,咱們只給出了部分示例代碼。
若是你須要完整的代碼,請點擊: https://github.com/mengyunzhi/springBootSampleCode/tree/master/multiQuery
本文開發環境:java:1.8
+ maven:3.3
html
在系統開發中,咱們避免不了要使用多條件查詢,好比咱們按學生的性氏來查找學生,按學生所在的班級查找學生,按學生入學的時間來查找學生等。在以往的開發中,進行綜合查詢時,咱們須要爲每一個實體單獨寫綜合查詢的代碼。具體思路爲:先將查詢條件加入map
,而後在綜合查詢時,取出map
中的值,並加入查詢條件中,最後實現綜合查詢。java
時間一長,咱們發如今查詢的過程當中,大多有如下規律:git
number(short int long ...)
時,大多進行的爲精確查詢。String
時,大多進行的爲模糊查詢。Collection
,大多進行爲in
查詢。Date
時,大多進行的爲範圍查詢。鑑於大多數符合以上規律,本文將闡述了一種簡單的方法,可以使得咱們像使用findById(Long id)
同樣,來使用pageByEntity(Object entity)
進行綜合條件查詢。github
有3個實體類:教師,班級,地址。一個實體基類,YunZhiAbstractEntity
三者的關係以下:spring
功能需求以下:
以klass
對象作爲查詢參數,按klass
對象中的屬性值來進行綜合查詢:
好比:sql
klass
對象的name
值爲zhangsan
,則模糊查出全部name
爲zhangsan
班級信息。klass
對象的totalStudentCount
值爲3
,則查出全部totalStudentCount
爲3
班級信息。klass
對象中的teacher
。則查詢時加入klass
->teacher
的信息。若是teacher
設置了ID
,則加入teacher
對應ID
的精確查詢,若是未設置ID
,則查詢teacher
的其它屬性,其屬性不爲null
,加入查詢條件。klass
對象中的teacher
中的address
,則按第3點的標準進行klass
->teacher
->address
的關聯查詢。本文大致將使用到如下知識點:api
基本思路:springboot
本文github
中給出的示例代碼,除in
查詢外,已經實現了其它幾點在本文中所須要的功能。本節中,只直接給出關鍵代碼,其它的關聯代碼,還須要到github
中進行查看。app
├── java │ └── com │ └── mengyunzhi │ └── springbootsamplecode │ └── multiquery │ ├── MultiQueryApplication.java │ ├── annotation │ │ ├── BeginQueryParam.java │ │ ├── EndQueryParam.java │ │ └── IgnoreQueryParam.java │ ├── entity │ │ ├── Address.java │ │ ├── Klass.java │ │ ├── Teacher.java │ │ └── YunZhiAbstractEntity.java │ ├── repository │ │ ├── AddressRepository.java │ │ ├── KlassRepository.java │ │ └── TeacherRepository.java │ └── service │ ├── AddressService.java │ ├── AddressServiceImpl.java │ ├── CommonService.java │ ├── KlassService.java │ ├── KlassServiceImpl.java │ ├── TeacherService.java │ ├── TeacherServiceImpl.java │ ├── YunzhiService.java │ └── YunzhiServiceImpl.java └── resources └── application.yml
實體類的繼承咱們在需求中已經給出。annotation
註解,repository
倉庫(dao)層,service
服務層中都很普通,它們是本文完成綜合查詢的基礎。最下方的 YunzhiServiceImpl.java
,即是咱們今天的主角。less
package com.mengyunzhi.springbootsamplecode.multiquery.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.List; /** * @author panjie */ public interface YunzhiService { /** * 經過傳入的實體進行多條件查詢 * @param entity * @param pageable * @return 分頁數據 * panjie */ Page<?> page(JpaSpecificationExecutor jpaSpecificationExecutor, Object entity, Pageable pageable); /** * 經過傳入的實體查詢全部數據 * @param entity * @return * panjie */ List<?> findAll(JpaSpecificationExecutor jpaSpecificationExecutor, Object entity); }
代碼中,已經給出詳細的註釋。請參閱。
package com.mengyunzhi.springbootsamplecode.multiquery.service; import com.mengyunzhi.springbootsamplecode.multiquery.annotation.BeginQueryParam; import com.mengyunzhi.springbootsamplecode.multiquery.annotation.EndQueryParam; import com.mengyunzhi.springbootsamplecode.multiquery.annotation.IgnoreQueryParam; import com.mengyunzhi.springbootsamplecode.multiquery.entity.YunZhiAbstractEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Service; import javax.persistence.criteria.*; import java.lang.reflect.Field; import java.sql.Date; import java.util.Calendar; import java.util.Collection; import java.util.List; /** * 多條件綜合查詢 * * @author panjie */ @Service public class YunzhiServiceImpl implements YunzhiService { private static final Logger logger = LoggerFactory.getLogger(YunzhiServiceImpl.class); @Override public Page<Object> page(JpaSpecificationExecutor jpaSpecificationExecutor, Object entity, Pageable pageable) { Specification specification = this.getSpecificationByEntity(entity); Page<Object> objectPage = jpaSpecificationExecutor.findAll(specification, pageable); return objectPage; } @Override public List<Object> findAll(JpaSpecificationExecutor jpaSpecificationExecutor, Object entity) { Specification specification = this.getSpecificationByEntity(entity); List<Object> objectList = jpaSpecificationExecutor.findAll(specification); return objectList; } private Specification getSpecificationByEntity(Object entity) { Specification<Object> specification = new Specification<Object>() { private Predicate predicate = null; private CriteriaBuilder criteriaBuilder; // 設置and謂語.注意,這裏只能設置and關係的謂語,若是謂語爲OR,則須要手動設置 private void andPredicate(Predicate predicate) { if (null != predicate) { if (null == this.predicate) { this.predicate = predicate; } else { this.predicate = this.criteriaBuilder.and(this.predicate, predicate); } } } private void generatePredicate(Object entity, From<Object, ?> root) { logger.debug("反射字段,按字段類型,進行綜合查詢"); Field[] fields = entity.getClass().getDeclaredFields(); try { for (Field field : fields) { logger.info("反射字段名,及字段值。並設置爲字段可見"); String name = field.getName(); field.setAccessible(true); Object value = field.get(entity); if (value != null) { if (field.getAnnotation(IgnoreQueryParam.class) != null) { logger.debug("存在@IgnoreQueryParam註解, 跳出"); continue; } // 初始化兩個界限的變量 Boolean isBegin = false; Boolean isEnd = false; // 查找開始與結束的註解 BeginQueryParam beginQueryParam = field.getAnnotation(BeginQueryParam.class); if (beginQueryParam != null) { logger.debug("存在@BeginQueryParam註解"); isBegin = true; name = beginQueryParam.name(); } else if (field.getAnnotation(EndQueryParam.class) != null) { logger.debug("存在@EndQueryParam註解"); isEnd = true; name = field.getAnnotation(EndQueryParam.class).name(); } // 按字段類型進行查詢 if (value instanceof String) { logger.debug("字符串則進行模糊查詢"); String stringValue = ((String) value); if (!stringValue.isEmpty()) { this.andPredicate(criteriaBuilder.like(root.get(name).as(String.class), "%" + stringValue + "%")); } } else if (value instanceof Number) { logger.debug("若是爲number,則進行精確或範圍查詢"); if (value instanceof Short) { Short shortValue = (Short) value; if (isBegin) { this.andPredicate(criteriaBuilder.greaterThanOrEqualTo(root.get(name).as(Short.class), shortValue)); } else if (isEnd) { this.andPredicate(criteriaBuilder.lessThanOrEqualTo(root.get(name).as(Short.class), shortValue)); } else { this.andPredicate(criteriaBuilder.equal(root.get(name).as(Short.class), shortValue)); } } else if (value instanceof Integer) { Integer integerValue = (Integer) value; if (isBegin) { this.andPredicate(criteriaBuilder.greaterThanOrEqualTo(root.get(name).as(Integer.class), integerValue)); } else if (isEnd) { this.andPredicate(criteriaBuilder.lessThanOrEqualTo(root.get(name).as(Integer.class), integerValue)); } else { this.andPredicate(criteriaBuilder.equal(root.get(name).as(Integer.class), integerValue)); } } else if (value instanceof Long) { Long longValue = (Long) value; if (isBegin) { this.andPredicate(criteriaBuilder.greaterThanOrEqualTo(root.get(name).as(Long.class), longValue)); } else if (isEnd) { this.andPredicate(criteriaBuilder.lessThanOrEqualTo(root.get(name).as(Long.class), longValue)); } else { this.andPredicate(criteriaBuilder.equal(root.get(name).as(Long.class), longValue)); } } else { logger.error("綜合查詢Number類型,暫時只支持到Short,Integer,Long"); } } else if (value instanceof Calendar) { logger.debug("Calendar類型"); Calendar calendarValue = (Calendar) value; if (isBegin) { this.andPredicate(criteriaBuilder.greaterThanOrEqualTo(root.get(name).as(Calendar.class), calendarValue)); } else if (isEnd) { this.andPredicate(criteriaBuilder.lessThanOrEqualTo(root.get(name).as(Calendar.class), calendarValue)); } else { this.andPredicate(criteriaBuilder.equal(root.get(name).as(Calendar.class), calendarValue)); } } else if (value instanceof Date) { logger.debug("Sql.Date類型"); Date dateValue = (Date) value; if (isBegin) { this.andPredicate(criteriaBuilder.greaterThanOrEqualTo(root.get(name).as(Date.class), dateValue)); } else if (isEnd) { this.andPredicate(criteriaBuilder.lessThanOrEqualTo(root.get(name).as(Date.class), dateValue)); } else { this.andPredicate(criteriaBuilder.equal(root.get(name).as(Date.class), dateValue)); } } else if (value instanceof YunZhiAbstractEntity) { logger.debug("是實體類"); YunZhiAbstractEntity yunZhiAbstractEntity = (YunZhiAbstractEntity) value; if (yunZhiAbstractEntity.getId() != null) { logger.debug("對應的ManyToOne,加入了id, 則按ID查詢"); this.andPredicate(criteriaBuilder.equal(root.join(name).get("id").as(Long.class), yunZhiAbstractEntity.getId())); } else { logger.debug("未加入id, 則進行Join查詢"); this.generatePredicate(value, root.join(name)); } } else if (value instanceof Collection) { Collection<?> collectionValue = (Collection<?>)value; if (collectionValue.size() > 0) { logger.warn("暫不支持一對多,多對多查詢"); // todo: 一對多,多對多查詢 } } else { logger.error("綜合查詢暫不支持傳入的數據類型", name, field); } } } } catch (Exception e) { e.printStackTrace(); } } @Override public Predicate toPredicate(Root<Object> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { // 設置CriteriaBuilder,用於合併謂語 this.criteriaBuilder = criteriaBuilder; this.generatePredicate(entity, root); return this.predicate; } }; return specification; } }
代碼中,已經給出詳細的註釋。請參閱。
package com.mengyunzhi.springbootsamplecode.multiquery.service; import com.mengyunzhi.springbootsamplecode.multiquery.entity.Address; import com.mengyunzhi.springbootsamplecode.multiquery.entity.Klass; import com.mengyunzhi.springbootsamplecode.multiquery.entity.Teacher; import com.mengyunzhi.springbootsamplecode.multiquery.repository.KlassRepository; import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.context.junit4.SpringRunner; import java.util.Calendar; import java.util.List; /** * @author panjie */ @SpringBootTest @RunWith(SpringRunner.class) public class YunzhiServiceImplTest { private final static Logger logger = LoggerFactory.getLogger(YunzhiServiceImplTest.class); @Autowired KlassService klassService; @Autowired YunzhiService yunzhiService; @Autowired TeacherService teacherService; @Autowired AddressService addressService; @Autowired KlassRepository klassRepository; @Test public void pageByEntity() { Klass originKlass = klassService.getOneSavedKlass(); PageRequest pageRequest = PageRequest.of(0, 2); Klass klass = this.initQueryKlass(); Page<Klass> klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("更改short值 ,斷言返回爲0"); klass.setTotalStudentCount((short) 11); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("設置short值爲null ,斷言返回爲1"); klass.setTotalStudentCount(null); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("更改int值 ,斷言返回爲0"); klass.setIntegerTest(101); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("設置int值爲null ,斷言返回爲1"); klass.setIntegerTest(null); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("更改long值 ,斷言返回爲0"); klass.setLongTest((long) 1001); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("設置long值爲null ,斷言返回爲1"); klass.setLongTest(null); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("測試關聯實體"); klass = this.initQueryKlass(); klass.setTeacher(originKlass.getTeacher()); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("更改關聯實體"); Teacher teacher = teacherService.getOneSavedTeacher(); klass.setTeacher(teacher); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("測試二級關聯實體Address"); teacher = new Teacher(); teacher.setAddress(originKlass.getTeacher().getAddress()); klass.setTeacher(teacher); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("更改測試二級關聯實體Address"); teacher.setAddress(addressService.getOneSavedAddress()); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("測試二級關聯實體 屬性"); Address address = new Address(); address.setCity("測試城市"); teacher.setAddress(address); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("測試二級關聯實體 屬性"); address.setCity("測試城市不存在"); teacher.setAddress(address); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("測試@IgnoreQueryParam註解"); klass.setTeacher(null); klass.setIgnoreTeacher(teacher); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("測試@BeginQueryParam, @EndQueryParam註解在Calendar上在做用"); klass = this.initQueryKlass(); teacher = new Teacher(); klass.setTeacher(teacher); Calendar beginCalendar = Calendar.getInstance(); beginCalendar.setTimeInMillis(originKlass.getTeacher().getCreateTime().getTimeInMillis()); Calendar endCalendar = Calendar.getInstance(); endCalendar.setTimeInMillis(originKlass.getTeacher().getCreateTime().getTimeInMillis()); teacher.setBeginCreateTime(beginCalendar); teacher.setEndCreateTime(endCalendar); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("將範圍擴大"); beginCalendar.add(Calendar.MINUTE, -1); endCalendar.add(Calendar.MINUTE, 1); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(1); logger.info("區間後移"); beginCalendar.add(Calendar.MINUTE, 2); endCalendar.add(Calendar.MINUTE, 2); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("區間前移"); beginCalendar.add(Calendar.MINUTE, -4); endCalendar.add(Calendar.MINUTE, -4); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("區間調換"); beginCalendar.add(Calendar.MINUTE, 4); klassPage = (Page<Klass>) yunzhiService.page(klassRepository, klass, pageRequest); Assertions.assertThat(klassPage.getTotalElements()).isEqualTo(0); logger.info("date的區間測試累了,不測了"); } @Test public void findAllByEntity() { logger.info("上面的分頁,已經將該測試的測試完了,這裏只是作作樣子,防止語法錯語"); Klass originKlass = klassService.getOneSavedKlass(); Klass klass = this.initQueryKlass(); List<Klass> klassPage = (List<Klass>) yunzhiService.findAll(klassRepository, klass); Assertions.assertThat(klassPage.size()).isEqualTo(1); logger.info("測試二級關聯實體Address"); Teacher teacher = new Teacher(); teacher.setAddress(originKlass.getTeacher().getAddress()); klass.setTeacher(teacher); klassPage = (List<Klass>) yunzhiService.findAll(klassRepository, klass); Assertions.assertThat(klassPage.size()).isEqualTo(1); } /** * 獲取初始化用於查詢的班級 * @return * panjie */ private Klass initQueryKlass() { logger.info("加入全部的測試信息,斷言返回1條記錄"); Klass klass = new Klass(); klass.setName("測試班級名稱"); klass.setTotalStudentCount((short) 10); klass.setIntegerTest(100); klass.setLongTest(1000L); return klass; } }
從第一次接觸spring-data-jpa
的綜合查詢,再到本身熟練使用,再到學生熟練使用,再到進行其它的項目維護時,見到了單表綜合查詢,再到今天的多實體綜合查詢。一步步的證實在學習--實踐--學習
的路上,咱們是對的。該方式也是當前,咱們能找到最有效的學習方法。
簡單的事情重複作,你就是專家;重複的事情認真作,你就是贏家。
學習的道路如此簡單:重複簡單的,思索重複的。你就是本身人生的贏家!
最後,感謝我聰明好學的學生們給了我在項目敲定完工日期後,仍然能夠靜心學習的自信;感謝spring-boot
,讓我在整個的學習過程當中,一次次的提高對面向接口開發思想的認識;感謝來自廊坊的張應亮先生的單表查詢代碼給了我實現多表關聯查詢的靈感與動力;感受廊坊世通科技的李廣川總經理對團隊的信任及支持。謝謝大家!