我们在项目中使用了Spring-Data-JPA后,面对多条件动态查询的业务场景时,JpaRepository提供的方法和自己写@Query已经满足了不了更加复杂的需求,此时,使用Specification接口及其实现类,可以帮我们实现动态查询。
Entity源码如下:
package com.company.project.model.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;
@Data
@Entity
public class UserInfoEntity {
@Id
private String id;
/**
* 头像
*/
private String portrait;
/**
* 昵称
*/
private String nickName;
/**
* 微信号
*/
private String wechatId;
/**
* 性别
*/
private String sex;
/**
* 地区Id
*/
private String districtId;
/**
* 个性签名
*/
private String signature;
/**
* 生日
*/
private Date birthday;
}
首先,按照Spring-Data规范,我们需要定义一个Repository,这个接口不但要继承Repository之外,还需要继承JpaSpecificationExecutor接口。
import com.company.project.model.entity.UserInfoEntity;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.Repository;
@org.springframework.stereotype.Repository
public interface UserInfoJpaSpecificationExecutor extends Repository<UserInfoEntity , String>, JpaSpecificationExecutor<UserInfoEntity>{
//其它方法声明
}
这是我们用于查询UserInfo的Repository,在JpaSpecificationExecutor接口的源码有如下方法声明:
package org.springframework.data.jpa.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
/**
* Interface to allow execution of {@link Specification}s based on the JPA criteria API.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public interface JpaSpecificationExecutor<T> {
/**
* Returns a single entity matching the given {@link Specification} or {@link Optional#empty()} if none found.
*
* @param spec can be {@literal null}.
* @return never {@literal null}.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
*/
Optional<T> findOne(@Nullable Specification<T> spec);
/**
* Returns all entities matching the given {@link Specification}.
*
* @param spec can be {@literal null}.
* @return never {@literal null}.
*/
List<T> findAll(@Nullable Specification<T> spec);
/**
* Returns a {@link Page} of entities matching the given {@link Specification}.
*
* @param spec can be {@literal null}.
* @param pageable must not be {@literal null}.
* @return never {@literal null}.
*/
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
/**
* Returns all entities matching the given {@link Specification} and {@link Sort}.
*
* @param spec can be {@literal null}.
* @param sort must not be {@literal null}.
* @return never {@literal null}.
*/
List<T> findAll(@Nullable 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. Can be {@literal null}.
* @return the number of instances.
*/
long count(@Nullable Specification<T> spec);
}
findOne(Specification<T> spec):根据条件查询获取一条数据;
findAll(Specification<T> spec) :根据条件查询获取全部数据;
findAll(Specification<T> spec, Pageable pageable) : 根据条件分页查询数据,pageable设定页码、一页数据量,同时返回的是Page类对象,可以通过getContent()方法拿到List集合数据;
findAll(Specification<T> spec, Sort sort) : 根据条件查询并返回排序后的数据;
count(Specification<T> spec) : 获取满足当前条件查询的数据总数;
那么,问题来了!现在只声明Repository没用,因为上述每个方法都要求传入一个Specification类型的对象,但是Specification是一个接口,不能直接获取或者对象。
package org.springframework.data.jpa.domain;
import static org.springframework.data.jpa.domain.Specifications.CompositionType.*;
import java.io.Serializable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.lang.Nullable;
/**
* Specification in the sense of Domain Driven Design.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Krzysztof Rzymkowski
* @author Sebastian Staudt
* @author Mark Paluch
*/
@SuppressWarnings("deprecation")
public interface Specification<T> extends Serializable {
long serialVersionUID = 1L;
/**
* Negates the given {@link Specification}.
*
* @param <T>
* @param spec can be {@literal null}.
* @return
* @since 2.0
*/
static <T> Specification<T> not(Specification<T> spec) {
return Specifications.negated(spec);
}
/**
* Simple static factory method to add some syntactic sugar around a {@link Specification}.
*
* @param <T>
* @param spec can be {@literal null}.
* @return
* @since 2.0
*/
static <T> Specification<T> where(Specification<T> spec) {
return Specifications.where(spec);
}
/**
* ANDs the given {@link Specification} to the current one.
*
* @param other can be {@literal null}.
* @return The conjunction of the specifications
* @since 2.0
*/
default Specification<T> and(Specification<T> other) {
return Specifications.composed(this, other, AND);
}
/**
* ORs the given specification to the current one.
*
* @param other can be {@literal null}.
* @return The disjunction of the specifications
* @since 2.0
*/
default Specification<T> or(Specification<T> other) {
return Specifications.composed(this, other, OR);
}
/**
* Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
* {@link Root} and {@link CriteriaQuery}.
*
* @param root must not be {@literal null}.
* @param query must not be {@literal null}.
* @param criteriaBuilder must not be {@literal null}.
* @return a {@link Predicate}, may be {@literal null}.
*/
@Nullable
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}