Spring Data JPA Criteria查询、部分字段查询

  Spring Data JPA系列

1、SpringBoot集成JPA及基本使用

2、Spring Data JPA Criteria查询、部分字段查询

3、Spring Data JPA数据批量插入、批量更新真的用对了吗

4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作

5、Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

6、【源码】Spring Data JPA原理解析之Repository的自动注入(一)

7、【源码】Spring Data JPA原理解析之Repository的自动注入(二)

8、【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码

9、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)

10、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)

11、【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理

12、【源码】SpringBoot事务注册原理

13、【源码】Spring Data JPA原理解析之事务注册原理

14、【源码】Spring Data JPA原理解析之事务执行原理

15、【源码】SpringBoot编程式事务使用及执行原理

16、【源码】Spring事务之传播特性的详解

17、【源码】Spring事务之事务失效及原理

18、【源码】Spring Data JPA原理解析之Hibernate EventListener使用及原理

19、【源码】Spring Data JPA原理解析之Auditing执行原理

20、Spring Data JPA使用及实现原理总结

前言

在上一篇SpringBoot集成JPA及基本使用-CSDN博客,里面讲解了通过Spring Data JPA的命名规范实现数据库查询以及自定义SQL语句查询。而在开发中,不定个数的多条件查询是一种很常见的场景,如根据注册起止日期、用户名、用户级别等查询用户,且其中的条件并不是必须填写的。使用Criteria查询可以高效的解决以上问题。在开始讲解之前,先了解一下JPQL。

JPQL语言

JPQL语言(Java Persistence Query Language)是一种和SQL非常类似的中间性和对象化查询语言,它最终会被编译成具体的地场数据库的SQL语言,从而屏蔽不同的数据库的差异。

JPQL是面向对象进行查询的语言,开发者可以通过访问持久化映射的实体类,以及类中的属性来编写类似SQL的语句。

JPQL语言通过Query接口封装执行,在Query中封装了数据库访问操作的相关方法。

JPA元模型

在JPA中,标准的查询是以元模型的概念为基础的。元模型是以具体持久化单元的受管实体定义的,实体可以是实体类、嵌入式类或映射的父类。提供受管实体元信息的类就是元模型类。使用元模型最大优势是可以在编译时访问实体的持久属性。

如上一篇SpringBoot集成JPA及基本使用-CSDN博客中定义的ProductEntity实体类对应的元模型类的名称为ProductEntity_,类中的属性全部是使用publict和static修饰的,类型为SingularAttribute。如下:

package com.jingai.jpa.dao.entity;

import java.util.Date;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(ProductEntity.class)
public abstract class ProductEntity_ {

	public static volatile SingularAttribute<ProductEntity, Date> validateTime;
	public static volatile SingularAttribute<ProductEntity, Date> createTime;
	public static volatile SingularAttribute<ProductEntity, String> name;
	public static volatile SingularAttribute<ProductEntity, String> deliveryNo;
	public static volatile SingularAttribute<ProductEntity, String> securityCode;
	public static volatile SingularAttribute<ProductEntity, Long> pid;
	public static volatile SingularAttribute<ProductEntity, String> customer;
	public static volatile SingularAttribute<ProductEntity, Integer> validateNum;

	public static final String VALIDATE_TIME = "validateTime";
	public static final String CREATE_TIME = "createTime";
	public static final String NAME = "name";
	public static final String DELIVERY_NO = "deliveryNo";
	public static final String SECURITY_CODE = "securityCode";
	public static final String PID = "pid";
	public static final String CUSTOMER = "customer";
	public static final String VALIDATE_NUM = "validateNum";

}

元模型并不需要手动创建,hibernate提供了自动生成的插件,只需引入依赖及相应的配置即可直动生成。

2.1 引入依赖

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <scope>provided</scope>
        </dependency>

2.2 在Idea中配置

本人使用的是

此时会在项目的target/generated-sources/annotations目录中自动生成对应添加@Entity实体类对应的元模型类。

如果显示的格式有问题,导致在代码中无法访问元模型类,可以设置annotations为Source Root。

2.3 Source Root设置

对Source Root不了解的,可以看一下

IDEA新建文件时没有找到java类文件_idea新建java类不见了-CSDN博客

元模型自动生成的设置不止一种,具体见https://docs.jboss.org/hibernate/jpamodelgen/1.3/reference/en-US/html/chapter-usage.html

Criteria查询

还是以上一篇的Product为例,根据创建的起止日期、名称以及客户名进行查询。

3.1 创建一个通用的分页查询的表单

package com.jingai.jpa.common.form;

import org.springframework.util.StringUtils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SearchBaseForm {

    private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    // 起始日期
    private String startDate;
    // 截止日期
    private String endDate;
    // 当前查询的页,从1开始
    private Integer pageIndex;
    // 每页的记录数
    private Integer pageSize;

    public Integer getPageIndex() {
        return pageIndex == null || pageIndex == 0 ? 1 : pageIndex;
    }

    public Integer getPageSize() {
        return pageSize == null || pageSize == 0 ? 20 : pageSize;
    }

    public Date getStartDate() {
        try {
            return StringUtils.hasText(startDate) ? FORMAT.parse(startDate) : null;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Date getEndDate() {
        try {
            return StringUtils.hasText(endDate) ? FORMAT.parse(endDate) : null;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }

    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
}

3.2 创建Product的查询表单

package com.jingai.jpa.common.form;

import lombok.Data;

@Data
public class ProductForm extends SearchBaseForm {

    private String name;
    private String customer;

}

3.3 Repository的修改

在上一篇的ProductRepository继承了JpaRepository,而这里要改为继承JpaRepositoryImplementation,其他不变。其中JpaRepositoryImplementation继承了JpaRepository,还继承了JpaSpecificationExecutor。此处的例子中需要用到JpaSpecificationExecutor的findAll()接口。

package com.jingai.jpa.dao;

import com.jingai.jpa.dao.entity.ProductEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;

import java.util.List;

public interface ProductRepository extends JpaRepositoryImplementation<ProductEntity, Long> {

    List<ProductEntity> findByPidBetween(long startPid, long endPid);

    @Query("from ProductEntity where name like ?1")
    List<ProductEntity> searchByName(String name);

}

3.4 Criteria分页查询

    @Override
    public Page<ProductEntity> listByPage(ProductForm form) {
        // 创建一个Specification,实现接口中的toPredicate()方法,该方法返回一个Predicate
        Specification<ProductEntity> specification = new Specification<ProductEntity>() {
            @Override
            public Predicate toPredicate(Root<ProductEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>(8);
                if(StringUtils.hasText(form.getName())) {
                    predicates.add(criteriaBuilder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
                }
                if(StringUtils.hasText(form.getCustomer())) {
                    predicates.add(criteriaBuilder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
                }
                if(form.getStartDate() != null) {
                    predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
                }
                if(form.getEndDate() != null) {
                    predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
                }
                return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
            }
        };
        // 创建排序字段,可设置多个
        Sort sort = Sort.by(Sort.Direction.DESC, ProductEntity_.createTime.getName());
        Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize(), sort);
        // 使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合
        return productRepository.findAll(specification, pageable);
    }

Specification接口中的toPredicate()接收三个参数,分别为Root<T> root、CriteriaQuery<?> query、CriteriaBuilder criteriaBuilder。

CriteriaBuilder:用于构造条件查询、复合选择、表达式、谓词和排序
CriteriaQuery:定义了特定于顶级查询的功能,包含了查询的各个部分。如:select结果集、where条件、group by、order by等。在CriteriaQuery指定返回值结果集。
Root:定义Criteria查询的根对象,Criteria查询的根定义为实体类型,它与SQL查询中的FROM子句类似,定义了查询的FROM子句中能够出现的类型。可以有多个查询根对象

通过CriteriaBuilder提供的like、equal、lessThan、greaterThan等方法,返回Predicate对象,作为CriteriaQuery的where条件。

Predicate过滤条件应用到SQL语句的where子句中。在Criteria查询中,查询条件通过Predicate或Expression实例应用到CriteriaQuery对象上。

Predicate实例也可以使用Expression的isNull、isNotNull、in方法获取,复合Predicate语句可以使用CriteriaBuilder的and、or、andnot方法构建。

部分字段查询

在CriteriaQuery中,可以通过select()方法设置返回值集合,但使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合,所以即使设置了也是返回ProductEntity实体。针对部分字段的查询,需要根据返回值信息,创建对应的CriteriaQuery对象。以下为查询部分字段的代码。

    @Override
    public Page<Object[]> listByPage2(ProductForm form) {
        // 获取一个builder
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        // createQuery中的传参为搜索结果的返回值类型,也就是结果集的泛型为Object[]数组
        CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
        // 查询的根对象,可以有多个查询根对象
        Root<ProductEntity> root = query.from(ProductEntity.class);
        // 设置查询的返回值信息。此处只查询pid和name
        query.multiselect(root.get(ProductEntity_.pid), root.get(ProductEntity_.name));
        // 添加搜索条件Predicate
        List<Predicate> predicates = new ArrayList<>(4);
        if(StringUtils.hasText(form.getName())) {
            predicates.add(builder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
        }
        if(StringUtils.hasText(form.getCustomer())) {
            predicates.add(builder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
        }
        if(form.getStartDate() != null) {
            predicates.add(builder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
        }
        if(form.getEndDate() != null) {
            predicates.add(builder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
        }
        Predicate[] predicateses = predicates.toArray(new Predicate[0]);
        // 设置搜索条件
        query.where(predicateses);
        // 设置排序规则
        query.orderBy(new OrderImpl(root.get(ProductEntity_.createTime), false));
        // 执行分页查询
        TypedQuery<Object[]> query1 = entityManager.createQuery(query);
        query1.setFirstResult((form.getPageIndex()) * form.getPageSize());
        query1.setMaxResults(form.getPageSize());
        List<Object[]> list = query1.getResultList();

        // 获取总记录数
        // 设置新的查询返回值类型
        CriteriaQuery<Long> queryCount = builder.createQuery(Long.class);
        // 查询的根对象
        root = queryCount.from(ProductEntity.class);
        // 设置返回值信息,查询count(pid)
        queryCount.select(builder.count(root.get(ProductEntity_.pid)));
        // 设置查询条件
        queryCount.where(predicateses);
        Long count = entityManager.createQuery(queryCount).getSingleResult();
        // 返回分页查询信息
        Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize());
        return new PageImpl<Object[]>(list, pageable, count);
    }

针对分页查询,需要执行两次查询,一次是查询数据,另一次是查询总记录数。

结尾

Spring Data JPA的知识点还有很多,限于篇幅,本篇先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 Spring Data JPA 提供的投影(Projection)功能来查询部分字段。 投影可以根据实体类的属性选择需要查询字段,返回一个自定义的接口或者一个DTO(Data Transfer Object)对象。 具体实现可以参考以下步骤: 1. 创建一个自定义接口或DTO对象,定义需要查询字段。 例如,需要查询用户表中的用户名和邮箱,可以创建一个自定义接口如下: ``` public interface UserProjection { String getUsername(); String getEmail(); } ``` 2. 在Repository接口中定义查询方法,使用`@Query`注解指定查询语句,并在查询语句中使用`SELECT`关键字选择需要查询字段。 例如,查询用户名和邮箱的方法可以定义如下: ``` public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u.username AS username, u.email AS email FROM User u WHERE u.id = :id") UserProjection findUsernameAndEmailById(@Param("id") Long id); } ``` 在查询语句中,使用`AS`关键字将查询结果映射到自定义接口中定义的属性中。 3. 在Service层调用Repository中定义的查询方法,获取需要查询字段。 例如,在Controller中调用Service中的方法: ``` @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public UserProjection getUser(@PathVariable Long id) { return userService.findUsernameAndEmailById(id); } } ``` 这样就可以查询到用户表中指定字段的数据了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值