欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringDataJPA之Specification復(fù)雜查詢實戰(zhàn)

 更新時間:2021年11月18日 14:22:21   作者:Moshow鄭鍇  
這篇文章主要介紹了SpringDataJPA之Specification復(fù)雜查詢實戰(zhàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

SpringDataJPA Specification復(fù)雜查詢

前言

繼上次SpringData-JPA之ExampleMatcher實例查詢使用一會之后發(fā)現(xiàn)ExampleMatcher對日期的查詢特別糟糕,所以才有了Specification查詢的研究。

  • 20200114:更新對JpaSpecificationExecutor的解析,Specification思路2,以及CriteriaBuilder +CriteriaQuery+Predicate+TypedQuery查詢部分
  • 20180811:根據(jù)所學(xué)所用,重新更新了文章,并增加了Pageable分頁排序功能。

實現(xiàn)

對應(yīng)的Repository需要實現(xiàn)JpaSpecificationExecutor接口

public interface EventRepository extends JpaRepository<Event, Integer> , JpaSpecificationExecutor<Event>{

Specification與Controller業(yè)務(wù)邏輯

 @GetMapping("/event/list")
 public ApiReturnObject findAllEvent(String eventTitle,Timestamp registerTime,Integer pageNumber,Integer pageSize) {
  if(pageNumber==null) pageNumber=1;
        if(pageSize==null) pageNumber=10;
  //分頁
        //Pageable是接口,PageRequest是接口實現(xiàn),new PageRequest()是舊方法,PageRequest.of()是新方法
        //PageRequest.of的對象構(gòu)造函數(shù)有多個,page是頁數(shù),初始值是0,size是查詢結(jié)果的條數(shù),后兩個參數(shù)參考Sort對象的構(gòu)造方法
        Pageable pageable = PageRequest.of(pageNumber,pageSize,Sort.Direction.DESC,"id");
        //Specification查詢構(gòu)造器
        Specification<Event> specification=new Specification<Event>() {
   private static final long serialVersionUID = 1L;
   @Override
   public Predicate toPredicate(Root<Event> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Predicate condition1 = null;
                if(StringUtils.isNotBlank(eventTitle)) {
                 condition1 = criteriaBuilder.like(root.get("eventTitle"),"%"+eventTitle+"%");
                }else {
                 condition1 = criteriaBuilder.like(root.get("eventTitle"),"%%");
                }
                Predicate condition2 = null;
                if(registerTime!=null) {
                 condition2 = criteriaBuilder.greaterThan(root.get("registerTime"), registerTime);
                }else {
                 condition2 = criteriaBuilder.greaterThan(root.get("registerTime"), new Timestamp(1514736000000L));
                }
                //Predicate conditionX=criteriaBuilder.and(condition1,condition2);
                //query.where(conditionX);
                query.where(condition1,condition2);
             //query.where(getPredicates(condition1,condition2)); //這里可以設(shè)置任意條查詢條件
                return null;  //這種方式使用JPA的API設(shè)置了查詢條件,所以不需要再返回查詢條件Predicate給Spring Data Jpa,故最后return null
   }
  };
  Page<Event> list=eventRepository.findAll(specification, pageable);        
        return ApiReturnUtil.page(list);
 }

ApiReturnUtil.page封裝

其實這個大家根據(jù)自己的項目自己封裝,這部分不作為核心內(nèi)容,知識之前有部分網(wǎng)友比較糾結(jié)這個工具,所以簡單放出來參考一下.

 public static ApiReturnObject page(Page returnObject) {
  return new ApiReturnObject(returnObject.getNumber()+"",returnObject.getNumberOfElements()+"",returnObject.getTotalElements()+"",returnObject.getTotalPages()+"","00","success",returnObject.getContent()); 
 }

ApiReturnObject的主要內(nèi)容:

 String errorCode="00";
 Object errorMessage;
 Object returnObject;
 String pageNumber;
 String pageSize;
 String totalElements;
 String totalPages;
 
 public ApiReturnObject(String pageNumber,String pageSize,String totalElements,String totalPages,String errorCode, Object errorMessage, Object returnObject) {
  super();
  this.pageNumber = pageNumber;
  this.errorCode = errorCode;
  this.errorMessage = errorMessage;
  this.returnObject = returnObject;
  this.pageSize = pageSize;
  this.totalElements = totalElements;
  this.totalPages = totalPages;
 }

查詢效果

返回對象有用的是pageNumber、pageSize、totalElements、totalPages等屬性,可對其進(jìn)行封裝

{
    "errorCode": "00",
    "errorMessage": "success",
    "pageNumber": "1",
    "pageSize": "2",
    "returnObject": [
        {
            "eventTitle": "1111",
            "id": 3,
            "registerTime": 1528702813000,
            "status": "0"
        },
        {
            "eventTitle": "小明失蹤",
            "id": 2,
            "registerTime": 1526268436000,
            "status": "0"
        }
    ],
    "totalElements": "5",
    "totalPages": "3"
}

可以查詢了。網(wǎng)上關(guān)于這個的資料也很少。希望可以幫到大家。

可能遇到的錯誤

Unable to locate Attribute with the the given name [event] on this ManagedType [org.microservice.tcbj.yytsg.checkcentersys.entity.Event]

出現(xiàn)這樣的情況,一般是因為實體類中沒有這個屬性,例如我Event的是eventTitle,寫成了event,就會報錯。

JpaSpecificationExecutor接口

20200114補充

JPA 提供動態(tài)接口JpaSpecificationExecutor,利用類型檢查的方式,利用Specification進(jìn)行復(fù)雜的條件查詢,比自己寫 SQL 更加便捷和安全.

public interface JpaSpecificationExecutor<T> {
 /**
  * Returns a single entity matching the given {@link Specification}.
  * 
  * @param spec
  * @return
  */
 T findOne(Specification<T> spec);
 /**
  * Returns all entities matching the given {@link Specification}.
  * 
  * @param spec
  * @return
  */
 List<T> findAll(Specification<T> spec);
 /**
  * Returns a {@link Page} of entities matching the given {@link Specification}.
  * 
  * @param spec
  * @param pageable
  * @return
  */
 Page<T> findAll(Specification<T> spec, Pageable pageable);
 /**
  * Returns all entities matching the given {@link Specification} and {@link Sort}.
  * 
  * @param spec
  * @param sort
  * @return
  */
 List<T> findAll(Specification<T> spec, Sort sort);
 /**
  * Returns the number of instances that the given {@link Specification} will return.
  * 
  * @param spec the {@link Specification} to count instances for
  * @return the number of instances
  */
 long count(Specification<T> spec);
}

Specification

Specification是我們傳入進(jìn)去的查詢參數(shù),是一個接口,并且只有一個方法

public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

一個一目了然的方法

第二個實現(xiàn)思路:聽說這個方法已經(jīng)過時了,其實這個方法是最好理解的.這里附上作為思路參考.

public Page<Even> findAll(SearchEven even) {
     Specification<Even> specification = new Specifications<Even>()
         .eq(StringUtils.isNotBlank(even.getId()), "id", even.getId())
         .gt(Objects.nonNull(even.getStatus()), "status", 0)
         .between("registerTime", new Range<>(new Date()-1, new Date()))
         .like("eventTitle", "%"+even.getEventTitle+"%")
         .build();
     return personRepository.findAll(specification, new PageRequest(0, 15));
}

Criteria+TypedQuery

思路三:利用EntityManager相關(guān)的CriteriaBuilder +CriteriaQuery+Predicate+TypedQuery進(jìn)行查詢.

@PersistenceContext
private EntityManager em;
/**
 * CriteriaBuilder 安全查詢創(chuàng)建工廠,創(chuàng)建CriteriaQuery,創(chuàng)建查詢具體具體條件Predicate
 * @author zhengkai.blog.csdn.net
 */
@Override
public List<Even> list(Even even) {
    //查詢工廠
	CriteriaBuilder cb = em.getCriteriaBuilder();
    //查詢類
	CriteriaQuery<Even> query = cb.createQuery(Even.class);
	//查詢條件
	List<Predicate> predicates = new LinkedList<>();
	//查詢條件設(shè)置
    predicates.add(cb.equal("id", even.getId()));
	predicates.add(cb.like("eventTitle", even.getEventTitle()));
	//拼接where查詢
    query.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
    //用JPA 2.0的TypedQuery進(jìn)行查詢
	TypedQuery<Even> typedQuery = em.createQuery(query);
    return typedQuery.getResultList();
}

開發(fā)過程中JPA Specification的應(yīng)用

Specification算是JPA里面比較靈活的查詢規(guī)范了,方便實現(xiàn)復(fù)雜的查詢方式。

為什么需要Specification

Spring-Data JPA 本身支持了比較簡單的查詢方式,也就是根據(jù)屬性名成結(jié)合一些規(guī)范編寫查詢方法,例如,一個Customer對象有name屬性,那么如果想要實現(xiàn)根據(jù)name來查詢,只需要在接口文件中添加一個方法findByName(String name)即可實現(xiàn)。

public interface CustomerRepository extends JpaRepository<Customer, Long> {
  Customer findByName(String name);
  Customer findByEmailAddress(String emailAddress);
  List<Customer> findByLastname(String lastname, Sort sort);
  Page<Customer> findByFirstname(String firstname, Pageable pageable);
}

但是在許多情況下,會有比較復(fù)雜的查詢,那么這個時候通過自動生成查詢方法的方式就不再可行。

應(yīng)用場景

為了實現(xiàn)復(fù)雜查詢,JPA提供了Criteria接口,這個是一套標(biāo)準(zhǔn)接口,來看一個例子,在一個平臺中,當(dāng)一個老客戶(注冊以來兩年)生日的時候,系統(tǒng)想要發(fā)送一個優(yōu)惠券給該用戶,那么傳統(tǒng)使用 JPA 2.0 Criteria API 去實現(xiàn):

LocalDate today = new LocalDate();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2); 
query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();
  • 首先獲得時間,去比較用戶的注冊時間
  • 接下來是獲得JPA中查詢使用的實例
  • 設(shè)置查詢條件,首先判斷今天是否為某個客戶的生日,然后判斷是否為老客戶
  • 執(zhí)行查詢條件,獲得滿足條件的用戶

這里面的主要問題就是代碼擴展性比較差,因為需要設(shè)置CriteriaBuilder, CriteriaQuery, Root,同時這部分的代碼可讀性比較差。

JPA Specification實現(xiàn)復(fù)雜查詢

Specification為了實現(xiàn)可重用的斷言,JPA 里面引入了一個Specification接口,接口的封裝很簡單,如下

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

在Java8中,我們可以非常方便地實現(xiàn)如上使用Criteria實現(xiàn)的效果

public CustomerSpecifications {
    public static Specification<Customer> customerHasBirthday() {
        return (root, query, cb) -> { 
            return cb.equal(root.get(Customer_.birthday), today);
        };
    }
    public static Specification<Customer> isLongTermCustomer() {
        return (root, query, cb) -> { 
            return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
        };
    }
}

這樣對應(yīng)JPA的repository實現(xiàn)就可以如下

customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());

而其實Specification為我們做的事情就是替我們準(zhǔn)備了CriteriaQuery, Root, CriteriaBuilder, 有了這些可重用的斷言之后,便可以將他們組合起來實現(xiàn)更加復(fù)雜的查詢了

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));

JPA多條件、多表查詢

如果需要使用Specification,那么對應(yīng)的Repository需要實現(xiàn)接口JpaSpecificationExecutor

public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {}

單表多條件查詢

在結(jié)合 Spring Boot 和 JPA 之后,為了四號線多條件查詢,并且整理分頁, 則可以考慮使用Predicate 斷言, 例如現(xiàn)在針對 User , 想要根據(jù)用戶的不同屬性進(jìn)行模糊查詢,同時如果屬性值為空或者空字符串,則跳過該屬性,不作為查詢條件,同時屬于單表多條件查詢,則

//在spring-jpa 2之后 不再使用 new PageRuest(page, pageSize) 的方式
Pageable pageable = PageRequest.of(page, pageSize);
//實現(xiàn)條件查詢,組合查詢
Specification<User> specification = new Specification<User>() {
    private static final long serialVersionUID = 1L;
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        String account = request.getAccount();
        String name = request.getName();
        String phone = request.getPhone();
        String accountType = request.getAccountType();
        String city = request.getCity();
        String type = request.getType();
        //用列表裝載斷言對象
        List<Predicate> predicates = new ArrayList<Predicate>();
        if(org.apache.commons.lang3.StringUtils.isNotBlank(name)) {
            //模糊查詢,like
            Predicate predicate = cb.like(root.get("name").as(String.class), "%" + name +"%");
            predicates.add(predicate);
        } 
        if (StringUtils.isNotBlank(account)) {
            Predicate predicate = cb.like(root.get("account").as(String.class), "%" + account +"%");
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(phone)) {
            //精確查詢,equal
            Predicate predicate = cb.equal(root.get("phoneNumber").as(String.class), phone);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(accountType)) {
            Predicate predicate = cb.equal(root.get("accountType").as(String.class), accountType);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(city)) {
            Predicate predicate = cb.equal(root.get("city").as(String.class), city);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(type)) {
            Predicate predicate = cb.equal(root.get("type").as(String.class), type);
            predicates.add(predicate);
        }
        //判斷是否有斷言,如果沒有則返回空,不進(jìn)行條件組合
        if (predicates.size() == 0) {
            return null;
        }
        //轉(zhuǎn)換為數(shù)組,組合查詢條件
        Predicate[] p = new Predicate[predicates.size()];
        return cb.and(predicates.toArray(p));
    }
};
//交給DAO處理查詢?nèi)蝿?wù)
Page<User> dataPages = userDAO.findAll(specification, pageable);

多表多條件查詢

在許多時候會面對多表多條件查詢,實現(xiàn)實例如下

//封裝查詢對象Specification
Specification<Courier> example = new Specification<Courier>() {
    @Override
    public Predicate toPredicate(Root<Courier> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //獲取客戶端查詢條件
        String company = model.getCompany();
        String courierNum = model.getCourierNum();
        Standard standard = model.getStandard();
        String type = model.getType();
        //定義集合來確定Predicate[] 的長度,因為CriteriaBuilder的or方法需要傳入的是斷言數(shù)組
        List<Predicate> predicates = new ArrayList<>();
        //對客戶端查詢條件進(jìn)行判斷,并封裝Predicate斷言對象
        if (StringUtils.isNotBlank(company)) {
            //root.get("company")獲取字段名
            //company客戶端請求的字段值
            //as(String.class)指定該字段的類型
            Predicate predicate = cb.equal(root.get("company").as(String.class), company);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(courierNum)) {
            Predicate predicate = cb.equal(root.get("courierNum").as(String.class), courierNum);
            predicates.add(predicate);
        }
        if (StringUtils.isNotBlank(type)) {
            Predicate predicate = cb.equal(root.get("type").as(String.class), type);
            predicates.add(predicate);
        }
        //多表的條件查詢封裝,這是和單表查詢的區(qū)別
        if (standard != null) {
            if (StringUtils.isNotBlank(standard.getName())) {
                //創(chuàng)建關(guān)聯(lián)對象(需要連接的另外一張表對象)
                //JoinType.INNER內(nèi)連接(默認(rèn))
                //JoinType.LEFT左外連接
                //JoinType.RIGHT右外連接
                Join<Object, Object> join = root.join("standard",JoinType.INNER);
                //join.get("name")連接表字段值
                Predicate predicate = cb.equal(join.get("name").as(String.class), standard.getName());
                predicates.add(predicate);
            }
        }
        //判斷結(jié)合中是否有數(shù)據(jù)
        if (predicates.size() == 0) {
            return null;
        }
        //將集合轉(zhuǎn)化為CriteriaBuilder所需要的Predicate[]
        Predicate[] predicateArr = new Predicate[predicates.size()];
        predicateArr = predicates.toArray(predicateArr);
        // 返回所有獲取的條件: 條件 or 條件 or 條件 or 條件
        return cb.or(predicateArr);
    }
};
//調(diào)用Dao方法進(jìn)行條件查詢
Page<Courier> page = courierDao.findAll(example, pageable);

Spring Data Jpa 簡單模糊查詢

在一些比較簡單的查詢條件下,不一定要使用 Specification 接口,比如

@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
  /**
   * username不支持模糊查詢,deviceNames支持模糊查詢
   * @param deviceNames 模糊查詢deviceNames
   * @param username 用戶名稱
   * @return {@link List<User>}
   */
  List<User> findAllByDeviceNamesContainingAndUsername(String deviceNames,String username);  
  /**
   * 其中username不支持模糊查詢,deviceNames支持模糊查詢
   * 傳入的deviceNames需要在前后添加%,否則可能返回的結(jié)果是精確查詢的結(jié)果
   * @param deviceNames 模糊查詢deviceNames
   * @param username 用戶名稱
   * @return {@link List<User>}
   */
  List<User> findAllByDeviceNamesLikeAndUsername(String deviceNames,String username); 
}

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot使用ExceptionHandler做異常處理

    SpringBoot使用ExceptionHandler做異常處理

    這篇文章主要介紹了SpringBoot使用ExceptionHandler做異常處理,這篇文章通過多種方法案例來介紹該項技術(shù)的使用,需要的朋友可以參考下
    2021-06-06
  • Java代碼中如何去掉煩人的“!=null”

    Java代碼中如何去掉煩人的“!=null”

    這篇文章主要介紹了Java代碼中去掉煩人的“!=null”,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • 手把手教你SpringBoot過濾器N種注冊方式

    手把手教你SpringBoot過濾器N種注冊方式

    這篇文章主要介紹了手把手教你SpringBoot過濾器N種注冊方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java設(shè)計模式中的觀察者模式

    Java設(shè)計模式中的觀察者模式

    觀察者模式定義對象之間的一種一對多的依賴關(guān)系,使得每當(dāng)一個對象的狀態(tài)發(fā)生變化時,其相關(guān)的依賴對象都可以得到通知并被自動更新。主要用于多個不同的對象對一個對象的某個方法會做出不同的反應(yīng)
    2023-02-02
  • 為什么阿里巴巴要求日期格式化時必須有使用y表示年

    為什么阿里巴巴要求日期格式化時必須有使用y表示年

    這篇文章主要介紹了為什么阿里巴巴要求日期格式化時必須有使用y表示年,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 實例講解MyBatis如何防止SQL注入

    實例講解MyBatis如何防止SQL注入

    這篇文章通過實例代碼介紹MyBatis如何防止SQL注入,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • SpringBoot2.x 參數(shù)校驗問題小結(jié)

    SpringBoot2.x 參數(shù)校驗問題小結(jié)

    這篇文章主要介紹了SpringBoot2.x 參數(shù)校驗一些問題總結(jié),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • Java對象轉(zhuǎn)Json,關(guān)于@JSONField對象字段重命名和順序問題

    Java對象轉(zhuǎn)Json,關(guān)于@JSONField對象字段重命名和順序問題

    這篇文章主要介紹了Java對象轉(zhuǎn)Json,關(guān)于@JSONField對象字段重命名和順序問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • SpringBoot3和ShardingSphere5框架實現(xiàn)數(shù)據(jù)分庫分表

    SpringBoot3和ShardingSphere5框架實現(xiàn)數(shù)據(jù)分庫分表

    這篇文章主要介紹了SpringBoot3和ShardingSphere5框架實現(xiàn)數(shù)據(jù)分庫分表的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • Spring中為bean指定InitMethod和DestroyMethod的執(zhí)行方法

    Spring中為bean指定InitMethod和DestroyMethod的執(zhí)行方法

    在Spring中,那些組成應(yīng)用程序的主體及由Spring IoC容器所管理的對象,被稱之為bean,接下來通過本文給大家介紹Spring中為bean指定InitMethod和DestroyMethod的執(zhí)行方法,感興趣的朋友一起看看吧
    2021-11-11

最新評論