Spring-data-jpa最全的查询语法总结,直入超神

524 阅读4分钟

🤵‍♂️ 个人主页:@香菜的个人主页,加 ischongxin ,备注csdn

✍🏻作者简介:csdn 认证博客专家,游戏开发领域优质创作者,华为云享专家,2021年度华为云年度十佳博主

🐋 希望大家多多支持,我们一起进步!😄

如果文章对你有帮助的话,

欢迎评论 💬点赞👍🏻 收藏 📂加关注+

系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点

前言

之前文章已经写过一次spring data,链接:spring-data 一统江湖,玩转多种数据源

主要的内容可以参考官方的:Spring Data JPA - Reference Documentation

今天这篇文章总结下spring data jpa的查询语法,在开发中能够灵活使用,在开始下面的之前我们定义一个entity,以便后面的部分使用

这里定义一个玩家的entity

@Data
@Entity
@Table(name = "player_info")
@ApiModel(description = "玩家信息")
public class PlayerEntity implements Serializable {
 
    private static final long serialVersionUID = 1L;   
        @Id    
        @Column(name = "id", nullable = false)
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @ApiModelProperty(value = "主键")
        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
        private Integer id;        
        
        @ApiModelProperty(value = "玩家名字")
        @Column(name = "player_name")
        private String playerName; 
               
        @ApiModelProperty(value = "玩家类型")
        @Column(name = "player_type")
        private String playerType;
   }

1、方法命名查询

这种方式是最简单的,也是最方便的,可能也是很多人包括我选择spring data jpa的主要原因之一,通过简单的组合就可以实现查询

1.1 例子几步走

1.1.1 声明一个扩展 Repository 的接口或其子接口之一

这里的entity使用PlayerEntity ,主键是用Integer

interface PlayerRepository extends Repository<PlayerEntity, Integer> { … }

1.1.2 声明查询方法

interface PlayerRepository extends Repository<PlayerEntity, Integer> {
  List<PlayerEntity> findByPlayerName(String playerName);
}

1.2 查询语法

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

image.png

2、SQL查询

这个是常用的一种方式,直接使用sql查询,使用query注解,并且nativeQuery = true,直白点就是使用sql查询

方法名可以根据自己习惯进行命名

public interface PlayerDao extends JpaRepository<PlayerEntity, Integer>, JpaSpecificationExecutor<PlayerEntity>{
 
    @Query(value = "SELECT max(0 + SUBSTR( player_id, 3 )) FROM parking_info WHERE LEFT ( player_id, 2 ) = :zoneId", nativeQuery = true)
    int getMaxPlayerId(String zoneId);
   }

这里的问题是没办法在开发的时候做语法检查,只能在调试中进行检查

3、JPQL查询

和原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。

这个和上面的区别在于nativeQuery = false,当然默认也是false,

@Query(value = "select companyCode from PlayerEntity where playerId = :playerId")
String findCompanyCodeByPlayerId(String playerId);

一定要注意,这里的字段和类名都是类中定义的

4、复杂查询 Specification

4.1 接口说明

需要Spring Data Jpa 支持 Criteria 查询方式,使用这种方式需要继承 JpaSpecificationExecutor 接口,该接口提供了如下一些方法

JpaSpecificationExecutor这个接口基本是围绕着Specification接口来定义的

public interface Specification<T> extends Serializable {
    long serialVersionUID = 1L;
 
    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));
        };
    }
 
    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : spec;
    }
 
    default Specification<T> and(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
    }
 
    default Specification<T> or(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
    }
 
    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}

通常来说我们一般使用toPredicate方法

Root:查询的根对象(查询的任何属性都可以从更对象获取)

CriteriaQuery:顶层查询对象,自定义查询方式(了解,一般不用)

CriteriaBuilder:查询构造器,封装了很多查询条件

4.2 看个官方的例子

4.2.1 定义查询条件

public class CustomerSpecs {
 
 
  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, builder) -> {
      LocalDate date = LocalDate.now().minusYears(2);
      return builder.lessThan(root.get(Customer_.createdAt), date);
    };
  }
 
  public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
    return (root, query, builder) -> {
      // build query here
    };
  }
}

4.2.2 调用查询方法

List customers = customerRepository.findAll(isLongTermCustomer());

4.2.3 项目中使用方法

Sort sort;
       if (order == null){
           sort = Sort.unsorted();
       }else {
           sort = Sort.by(order? Sort.Direction.DESC:Sort.Direction.ASC,"totalParkingSpace");
       }
       Pageable pageable = PageRequest.of(page, size,sort);
       Specification<ParkingInfoEntity> sp = (root, query, cb) -> {
           List<Predicate> condition = new ArrayList<>();
           if (parkingType != null) {
               Predicate parkingTypePredicate = cb.equal(root.get("parkingType"), parkingType);
               condition.add(parkingTypePredicate);
           }
           if (StringUtils.hasText(keyword)) {
               String likeKeyword = "%" + keyword + "%";
               Predicate parkingId = cb.like(root.get("parkingId"), likeKeyword);
               Predicate parkingName = cb.like(root.get("parkingName"), likeKeyword);
               Predicate parkingAddress = cb.like(root.get("parkingAddress"), likeKeyword);
               Predicate belongCompany = cb.like(root.get("belongCompany"), likeKeyword);
               Predicate or = cb.or(parkingId, parkingName, parkingAddress, belongCompany);

               condition.add(or);
           }

           return cb.and(condition.toArray(new Predicate[0]));
       };
       Page<ParkingInfoEntity> all = parkingInfoDao.findAll(sp, pageable);

注:这种方式主要应用在复杂条件查询时候,可以简单的理解为手动拼接sql,只不过这里更安全,更不容易出错。

用过的都说好

4.3 调用数据库内置函数

CriteriaBuilder还提供了function方法,在function方法里可以直接传方法名进去

/**
name: 方法名
returnType: 返回类型
arguments:表达式
**/
public <T> Expression<T> function(String name, Class<T> returnType, Expression... arguments) {
        return new ParameterizedFunctionExpression(this, returnType, name, arguments);
    }

调用代码如下

Expression round = cb.function("round", BigDecimal.class, quot); cb.greaterThanOrEqualTo(round, 80)

5、QueryByExampleExecutor

它允许动态查询创建,并且不需要编写包含字段名称的查询

5.1 用法

Query by Example API 由四部分组成

  • 实例,entity的实例
  • ExampleMatcher,ExampleMatcher包含有关如何匹配特定字段的详细信息。它可以在多个示例中重复使用。
  • Example,查询用的对象,包含查询的匹配方式和字段的值(entity实例)

5.2 实例

它用于创建查询

Person person = new Person();                          
person.setFirstname("Dave");                           
 
ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcher(StringMatcher.ENDING);            
Example<Person> example = Example.of(person, matcher);

6、总结

简单查询使用 查询方法组合

复杂查询使用SQL查询和JPQL查询

动态查询使用Specification和Example API

在工作中根据自己的需求选择对应的方式