【Spring Data JPA】07 Specifications动态查询
【前言说明】
针对CRUD种的查询,因为我们的查询总是具有各种各样的筛选条件
为了我们的程序能够更加适应筛选条件的变化,SpringDataJpa提供了Specifications这种解决方案
Specifications 本意表示规范
也就是说我们的筛选条件也将需要被规范化
按照SpringDataJpa设计好的方式执行即可
【接口说明】
所在包位置:
org.springframework.data.jpa.repository.JpaSpecificationExecutor;
接口名称:Java持久化接口规范处理器
所有抽象方法:
    Optional<T> findOne(@Nullable Specification<T> var1);
    List<T> findAll(@Nullable Specification<T> var1);
    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
    List<T> findAll(@Nullable Specification<T> var1, Sort var2);
    long count(@Nullable Specification<T> var1);
1、所有方法的Specification参数都被注解@Nullable,表示这个参数可以为Null,即表明可以无Specification条件来执行
2、findOne是表明查询单个记录,Specification即表明是一个筛选条件的对象
3、两种查询所有的findAll,其中一种必须要求排序参数,用于确定的排序需求使用
4、我们知道分页必须要两个SQL执行,所以这里就有了Page & Long ,写过分页功能的就一定知道是组合使用的
查看这个Specification,发现它也是一个接口
org.springframework.data.jpa.domain
public interface Specification<T> extends Serializable
在我们之前的学习中我们的筛选条件越来越多,我们不可能再为各个筛选条件编写对应的参数
所以那个时候我们就需要统一起来各种筛选条件需要的参数就全部归纳为一个类,这就是筛选条件的参数类
但是不同的实体映射类,即我们的表的需要完成的功能不一样,自然而然筛选的条件也不一样
固定的一个条件参数类依然无法满足更多ORM的需要,则进一步上升为一个条件参数规范
所以这就是Specification
而具体的条件细节则由我们自己来完成:
【虽然他已经设定好默认的一些东西了。。。】
    static <T> Specification<T> not(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : (root, query, builder) -> {
            return builder.not(spec.toPredicate(root, query, builder));
        };
    }
    @Nullable
    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : spec;
    }
    @Nullable
    default Specification<T> and(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, (builder, left, rhs) -> {
            return builder.and(left, rhs);
        });
    }
    @Nullable
    default Specification<T> or(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, (builder, left, rhs) -> {
            return builder.or(left, rhs);
        });
    }
    @Nullable
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
对toPredicate的介绍:
要求参数:
Root 查询的根对象(查询的任何属性从根对象获取)
CriteriaQuery 顶层查询对象,自定义查询方式
CriteriaQueryBuilder 查询构建器 封装了很多查询条件
单个记录单个条件的查询:
实现这个规范接口,并且重写条件方法
从root对象获取筛选条件的字段【我需要根据什么字段来执行筛选条件?】
通过该Path对象被条件构建器注入和比较值进行比较
返回这个规范结果给我们的Dao方法使用
@Test /* 查询单个记录 单个查询条件 */
public void findOne() {
Specification<User> userSpecification = new Specification<User>(){ public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
// 获取比较的属性
Path<Object> user_id = root.get("user_id");
// 构建筛选条件 需要比较的字段,字段的值
// Predicate predicate = criteriaBuilder.equal(user_id, 2);
return criteriaBuilder.equal(user_id, 2); // 返回我们的比较结果?
}
}; Optional<User> optionalUser = userRepository.findOne(userSpecification);
User user = optionalUser.get();
System.out.println(user);
}
单个记录多个条件的查询:
使用构建器的and & or 来合并条件
@Test /* 查询单个记录 查询多个条件 */
public void findOnes() { /* Lambda表达式 */
Specification<User> userSpecification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
// 获取比较的属性
Path<Object> user_id = root.get("user_id");
Path<Object> user_name = root.get("user_name"); Predicate predicate01 = criteriaBuilder.equal(user_id, 2);
Predicate predicate02 = criteriaBuilder.equal(user_name, "user01"); // 如果多条件就合并条件
// criteriaBuilder.and(predicate01, predicate02)
// 或者不是全要求的条件,多条件其中一个满足的情况
// criteriaBuilder.or(predicate01, predicate02)
// 具体情况根据实际需求来抉择,或者组合 return criteriaBuilder.and(predicate01, predicate02);
}; Optional<User> optionalUser = userRepository.findOne(userSpecification);
User user = optionalUser.get();
System.out.println(user);
}
模糊条件的查询:
@Test /* 查询多个记录 查询条件:模糊查询 */
public void findOneByLike() { /* Lambda表达式 */
Specification<User> userSpecification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
// 获取比较的属性
Path<Object> user_name = root.get("user_name");
// 模糊要求指定参数类型
return criteriaBuilder.like(user_name.as(String.class), "%user%");
}; List<User> userList = userRepository.findAll(userSpecification);
for (User user : userList) {
System.out.println(user);
}
}
多记录排序条件查询:
@Test /* 查询多个记录 查询条件:模糊查询, 排序查询 */
public void findOneByLikeAndSort() { /* Lambda表达式 */
Specification<User> userSpecification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
// 获取比较的属性
Path<Object> user_name = root.get("userName");
// 模糊要求指定参数类型
return criteriaBuilder.like(user_name.as(String.class), "%use%");
}; // 参数?
// Sort sortOrders = new Sort(Sort.Direction.DESC, "user_name"); Sort sort = Sort.by(Sort.Direction.DESC, "userName"); List<User> userList = userRepository.findAll(userSpecification, sort); for (User user : userList) {
System.out.println(user);
}
}
排序条件查询注入的属性要求:
注意这里排序使用的对象属性!
因为这个错误导致我的实体类必须做出更改
package cn.echo42.domain; import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; import javax.persistence.*; /**
* @author DaiZhiZhou
* @file Spring-Data-JPA
* @create 2020-07-31 22:56
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "sys_user")
public class User { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Integer userId; // @Column(name = "user_id")
@Column(name = "user_name")
private String userName; // @Column(name = "user_name")
@Column(name = "user_password")
private String userPassword; // @Column(name = "user_password")
@Column(name = "user_status")
private Integer userStatus; // @Column(name = "user_status")
@Column(name = "user_is_del")
private Integer userIsDel; // @Column(name = "user_is_del")
}
详细原因是因为,排序的String参数properties,会将下划线作为字段的分隔符
我的user_name,就被分成user,name。这样就无法匹配了啊。
解决方案是被迫更改实体类的属性字段为驼峰命名方式,并且注解上对应的表字段
参考地址:
https://zhidao.baidu.com/question/1823902339135786828.html
排序查询的Sort条件对象的分析:
视频参考地址:
https://www.bilibili.com/video/BV1WJ411j7TP?t=271&p=67
在老版本的SpringDataJPA中,原先的Sort允许被直接带参构造的NEW出来
Sort sortOrders = new Sort(Sort.Direction.DESC, "user_name");
但是在现在的新版本中不再允许:
Sort的构造器不再允许被外部访问
    private Sort(Sort.Direction direction, List<String> properties) {
        if (properties != null && !properties.isEmpty()) {
            this.orders = (List)properties.stream().map((it) -> {
                return new Sort.Order(direction, it);
            }).collect(Collectors.toList());
        } else {
            throw new IllegalArgumentException("You have to provide at least one property to sort by!");
        }
    }
    @Generated
    protected Sort(List<Sort.Order> orders) {
        this.orders = orders;
    }
第一种方式要求Direction对象和一个Properties的集合,
Direction是一个枚举类,意思是顺序要求,无非就是ASC & DESC
Properties就是我们需要排序的字段,一个或者是多个的存在
但是现在这个构造器不可使用了。。。
第二种方式要求一个Order对象的集合,SpringDataJPA的设计者希望由Order对象来封装排序条件,
再装入Sort处理,每一个Order都是对应的字段和顺序的要求。
这也反映出来,第一种的弊端就是只能对所有的字段同时ASC或者DESC,并不能分开要求
但是Sort类提供了静态方法来实现对象的创建:
第一种提供properties属性即可,要求的字段均以默认的ASC排序
    public static Sort by(String... properties) {
        Assert.notNull(properties, "Properties must not be null!");
        return properties.length == 0 ? unsorted() : new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
    }
第二种提供Orders对象集合,对各个字段的排序要求是独立的
    public static Sort by(List<Sort.Order> orders) {
        Assert.notNull(orders, "Orders must not be null!");
        return orders.isEmpty() ? unsorted() : new Sort(orders);
    }
第三种就是由可变参数实现,和第二种区别不大
    public static Sort by(Sort.Order... orders) {
        Assert.notNull(orders, "Orders must not be null!");
        return new Sort(Arrays.asList(orders));
    }
第四种就是指定顺序条件,第一种的补充
    public static Sort by(Sort.Direction direction, String... properties) {
        Assert.notNull(direction, "Direction must not be null!");
        Assert.notNull(properties, "Properties must not be null!");
        Assert.isTrue(properties.length > 0, "At least one property must be given!");
        return by((List)Arrays.stream(properties).map((it) -> {
            return new Sort.Order(direction, it);
        }).collect(Collectors.toList()));
    }
多记录分页条件查询:
分页条件SpringDataJPA要求一个Pageable类型的对象传入
Pageable是一个接口,寓意可翻页的
实现类有一个PageRequest
打开PageRequest,同样的,不允许调用构造器创建对象
    protected PageRequest(int page, int size, Sort sort) {
        super(page, size);
        Assert.notNull(sort, "Sort must not be null!");
        this.sort = sort;
    }
但是它由和Sort一样提供了三种静态方法:
    public static PageRequest of(int page, int size) {
        return of(page, size, Sort.unsorted());
    }
    public static PageRequest of(int page, int size, Sort sort) {
        return new PageRequest(page, size, sort);
    }
    public static PageRequest of(int page, int size, Direction direction, String... properties) {
        return of(page, size, Sort.by(direction, properties));
    }
第一个仅仅要求当前页数和每页显示的记录数量
第二个多了一个排序对象要求,即我们分页之后再对这个结果集排序【盲猜】
第三个就是多字段统一顺序条件要求
对page条件的纠结:
注意这里的page参数,以往我们的SQL的LIMIT查询
是startPosition & sizeLimitation,即起始位置和记录长度限制
分页的页数需要转换成起始位置进行查询
也就是这个:
(当前页码 - 1)* 每页显示数量 = 起始位置
在SpringDataJPA这里已经帮我们处理好了,但是起始位置是从0开始
【需要处理一个再减一。。。】
演示案例:
@Test /* 查询多个记录 查询条件:分页查询 */
public void findAllByPaging() { /* Lambda表达式 */
Specification<User> userSpecification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
// 获取比较的属性
Path<Object> user_name = root.get("userName");
// 模糊要求指定参数类型
return criteriaBuilder.like(user_name.as(String.class), "%use%");
}; Pageable pageable = PageRequest.of(1,2); Page<User> userPage = userRepository.findAll(userSpecification, pageable);
List<User> userList = userPage.getContent(); for (User user : userList) {
System.out.println(user);
} // 获取总记录数量 long totalElements = userPage.getTotalElements();
// 获取总页数 int totalPages = userPage.getTotalPages();
}
【Spring Data JPA】07 Specifications动态查询的更多相关文章
- Spring Data JPA 的 Specifications动态查询
		主要的结构: 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询. ... 
- Spring Data JPA中的动态查询 时间日期
		功能:Spring Data JPA中的动态查询 实现日期查询 页面对应的dto类private String modifiedDate; //实体类 @LastModifiedDate protec ... 
- Spring data jpa 实现简单动态查询的通用Specification方法
		本篇前提: SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法 这篇文章中的第二种方法 实现Specification 这块的方法 只适用于一个对象针对某一个固定字 ... 
- Spring Data JPA进阶——Specifications和Querydsl
		Spring Data JPA进阶--Specifications和Querydsl 本篇介绍一下spring Data JPA中能为数据访问程序的开发带来更多便利的特性,我们知道,Spring Da ... 
- spring data jpa使用原生sql查询
		spring data jpa使用原生sql查询 @Repository public interface AjDao extends JpaRepository<Aj,String> { ... 
- spring data jpa 利用@Query进行查询
		参照https://blog.csdn.net/yingxiake/article/details/51016234#reply https://blog.csdn.net/choushi300/ar ... 
- Spring Data Jpa的四种查询方式
		一.调用接口的方式 1.基本介绍 通过调用接口里的方法查询,需要我们自定义的接口继承Spring Data Jpa规定的接口 public interface UserDao extends JpaR ... 
- Spring MVC和Spring Data JPA之按条件查询和分页(kkpaper分页组件)
		推荐视频:尚硅谷Spring Data JPA视频教程,一学就会,百度一下就有, 后台代码:在DAO层继承Spring Data JPA的PagingAndSortingRepository接口实现的 ... 
- spring data jpa实现多条件查询(分页和不分页)
		目前的spring data jpa已经帮我们干了CRUD的大部分活了,但如果有些活它干不了(CrudRepository接口中没定义),那么只能由我们自己干了.这里要说的就是在它的框架里,如何实现自 ... 
- Spring Data Jpa (四)注解式查询方法
		详细讲解声明式的查询方法 1 @Query详解 使用命名查询为实体声明查询是一种有效的方法,对于少量查询很有效.一般只需要关心@Query里面的value和nativeQuery的值.使用声明式JPQ ... 
随机推荐
- 探索Native Plugins:开启大模型的技能之门
			前言 上一章节我们了解了一下Semantic Kernnel中Plugins插件的概念以及学习了的 Semantic Kernel 模板插件的创建,本章节我们来学习 Native Plugins 原生 ... 
- linux系统下,安装maven教程
			1.下载 官网:https://maven.apache.org/download.cgi 2.上传包 将下载好的maven安装包apache-maven-3.8.6-bin.tar.gz放在磁盘的 ... 
- C#.NET WINFORM 缓存 System.Runtime.Caching MemoryCache
			C#.NET WINFORM 缓存 System.Runtime.Caching MemoryCache 工具类: using System; using System.Runtime.Caching ... 
- FlashDuty Changelog 2023-10-30 | 告警路由与 Slack 应用
			FlashDuty:一站式告警响应平台,前往此地址免费体验! 告警路由 什么是告警路由? FlashDuty已经与Zabbix.Prometheus等监控系统实现无缝集成,通过一个简单的webhook ... 
- 使用Git命令从本地上传到码云
			Gitee创建仓库内没有内容 本地: 初始化Git仓库:git init 提交文件到暂存区:git add . //. 表示提交所有文件 提交文件到工作区:git commit -m "此次 ... 
- Vue学习:20.综合案例-商品列表
			学而时用之,方能融会贯通! 实例:商品列表 实现功能 要求表格组件支持动态数据渲染.自定义表头和主体.标签组件需要双击显示输入框并获得焦点,可以编辑标签信息. 思路 首先是表格组件,动态渲染需要使用组 ... 
- jsp表单提交中的逻辑判断
			针对于表单 通常情况下 我们都是表单提交 提交的路径为以下: 提交的按钮的type="submit" 当我们想在表单提交前增加一个逻辑判断 我们就需要把button中的typ ... 
- OAuth + Security - 4 - 客户端信息存储数据库
			PS:此文章为系列文章,建议从第一篇开始阅读. 在之前的所有配置中,我们的客户端信息和授权码模式下的授权码任然还是存储在数据库中的,这样就不利于我们后期的扩展,所以在正式的生成环境中,我们一般将其存储 ... 
- idea设置jdk和设置文件编码格式utf-8
			1.idea设置jdk 2.idea设置文件编码格式utf-8 create utf-8 files with NO BOM 不要更改,否则编译会出错误. 
- python的requirements.txt_维护项目依赖包
			pycharm没有类似maven用于管理依赖包的工具,当一个项目在新的环境运行前,需要将对应依赖的包下载回来,如果一个个下载,会出现缺漏或版本号不对应的情况,这个时候可以用requirements.t ... 
