programing

스프링 부츠 & JPA:선택적이고 범위가 넓은 기준으로 검색 쿼리 구현

madecode 2023. 7. 10. 23:27
반응형

스프링 부츠 & JPA:선택적이고 범위가 넓은 기준으로 검색 쿼리 구현

이것은 SSCCE이고, 연구를 보여주며, 속임수가 아니며 주제에 있습니다!!!


스프링 부트 REST 서비스와 MySQL입니다.나는 다음을 가지고 있습니다.Profile엔티티:

@Entity
@Table(name = "profiles")
public class Profile extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "profile_given_name")
    private String givenName;

    @Column(name = "profile_surname")
    private String surname;

    @Column(name = "profile_is_male")
    private Integer isMale;

    @Column(name = "profile_height_meters", columnDefinition = "DOUBLE")
    private BigDecimal heightMeters;

    @Column(name = "profile_weight_kilos", columnDefinition = "DOUBLE")
    private BigDecimal weightKilos;

    @Column(name = "profile_dob")
    private Date dob;

    // Getters, setters & ctor down here
}

저도 있습니다.ProfileController 유연하고 강력한 검색 방법을 하는 GET 끝점을 노출하고 .Profiles광범위한 기준에 기초함:

# Search for women between 1.2 and 1.8 meters tall.
GET /v1/profiles?isMale=0&heightMeters={"gt": 1.2, "lt": 1.8}

# Search for men born after Jan 1, 1990 who weigh less than 100 kg.
GET /v1/profiles?isMale=1&dob={"gt" : "1990-01-01 00:00:00"}&weightKilos={"lt": 100.0}

기타.

여기 제 컨트롤러가 있습니다.

@RestController
@RequestMapping("/v1/profiles")
public class ProfileResource {
  @Autowired
  ProfileRepository profileRepository;

  @GetMapping
  public ResponseEntity<Set<Profile>> searchProfiles(@RequestParam(value = "isMale", required = false) String isMaleVal,
                                              @RequestParam(value = "heightMeters", required = false) String heightMetersVal,
                                              @RequestParam(value = "weightKilos", required = false) String weightKilosVal,
                                              @RequestParam(value = "dob", required = false) String dobVal) {

      Integer isMaleVal;
      BooleanCriteria isMaleCriteria;
      if(isMaleVal != null) {
        // Parse the value which could either be "0" for female, "1" for male or something like
        // ?isMale={0,1} to indicate

        // BooleanCriteria would store which values male, female or both) to include in the search
      }

      BigDecimal heighMeters;
      BigDecimalCriteria heightCriteria;
      if(heightMetersVal != null) {
        // Parse the value which like in the examples could be something like:
        // ?heightMeters={"gt" : "1.0"}

        // BigDecimalCriteria stores range information
      }

      BigDecimal heighMeters;
      BigDecimalCriteria weightCriteria;
      if(weightKilosVal != null) {
        // Parse the value which like in the examples could be something like:
        // ?weightKilos={"eq" : "100.5"}

        // BigDecimalCriteria stores range information
      }

      // Ditto for DOB and DateCriteria

      // TODO: How to pack all of these "criteria" POJOs into a
      // CrudRepository/JPQL query against the "profiles" table?
      Set<Profile> profiles = profileRepository.searchProfiles(
        isMaleCriteria, heightCriteria, weightCriteria, dobCriteria);
    }
}

를 들면, 엔생각자, 면하말내,BigDecimalCriteria다음과 같은 것이 될 수 있습니다.

// Basically it just stores the (validated) search criteria that comes in over the wire
// on the controller method
public class BigDecimalCriteria {
  private BigDecimal lowerBound;
  private Boolean lowerBoundInclusive;
  private BigDecimal upperBound;
  private Boolean upperBoundInclusive;

  // Getters, setters, ctors, etc.
}

이 모든 검색 기준은 선택 사항이므로 다음과 같이 할 수 있습니다.null , )에서를 작성하는 고민하고 있습니다.ProfileRepository:

public interface ProfileRepository extends CrudRepository<Profile,Long> {
  @Query("???")
  public Set<Profile> searchProfiles();
}

을 어떻게 할 수 ?@Query(...)위해서ProfileRepository#searchProfiles모든 검색 기준(검색할 수 있는 모든 허용 범위 및 기준 값이 주어짐)을 활성화하고 모든 기준이 null/선택 사항이 되도록 허용하는 방식?

물론, 작은 도서관이 많거나 Spring Boot/J.PA는 이미 이에 대한 해결책을 가지고 있습니다. 저는 귀를 기울입니다!

다음을 통해 사양이 포함된 복잡한 쿼리를 수행할 수 있습니다.JpaSpecificationExecutor춘계 자료로리포지토리 인터페이스는 다음을 확장해야 합니다.JpaSpecificationExecutor<T>할 수 . 새로운 쿼리를 생성하여 쿼리 조건을 지정할 수 있습니다.Specification<T>물건들.

를 Specification 인터페이스와 입니다.JpaSpecificationExecutor예는 다음과 같습니다.

@Entity
@Table(name = "person")
public class Person {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 private Long id;

 @Column(name = "name")
 private String name;

 @Column(name = "surname")
 private String surname;

 @Column(name = "city")
 private String city;

 @Column(name = "age")
 private Integer age;

        ....

}

그런 다음 저장소를 정의합니다.

public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {

}

했습니다.JpaSpecificationExecutor이 인터페이스는 사양 클래스를 통해 검색을 수행하는 방법을 정의합니다.

이제 우리가 해야 할 일은 다음을 반환할 사양을 정의하는 것입니다.Predicate 조건 포함(예: " " " " " " " " "PersonSpecification= ? 예: = ? 및= ? 에서 select * 하는 중입니다. = ? (예: = ? = ?)의 경우 select *를 선택합니다.

public class PersonSpecification implements Specification<Person> {

    private Person filter;

    public PersonSpecification(Person filter) {
        super();
        this.filter = filter;
    }

    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> cq,
            CriteriaBuilder cb) {

        Predicate p = cb.disjunction();

        if (filter.getName() != null) {
            p.getExpressions()
                    .add(cb.equal(root.get("name"), filter.getName()));
        }

        if (filter.getSurname() != null && filter.getAge() != null) {
            p.getExpressions().add(
                    cb.and(cb.equal(root.get("surname"), filter.getSurname()),
                            cb.equal(root.get("age"), filter.getAge())));
        }

        return p;
    }
}

이제 그것을 사용할 시간입니다.다음 코드 조각은 방금 만든 사양을 사용하는 방법을 보여줍니다.

...

Person filter = new Person();
filter.setName("Mario");
filter.setSurname("Verdi");
filter.setAge(25);

Specification<Person> spec = new PersonSpecification(filter);

List<Person> result = repository.findAll(spec);

다음은 github의 전체 예입니다.

또한 사양을 사용하여 복잡한 쿼리를 만들 수 있습니다.

Querydsl 및 웹 지원 Spring Data 확장의 도움으로 필요한 거의 모든 것이 Spring Data에 이미 구현되어 있습니다.

당신은 또한 당신의 레포를 확장해야 합니다.QuerydslPredicateExecutor또한 Spring Data REST를 사용하는 경우 기본 필터링, 페이징 및 정렬 지원을 통해 repo 데이터를 바로 '박스에서' 쿼리할 수 있습니다.

/profiles?isMale=0&heightMeters=1.7&sort=dob,desc&size=10&page=2

보다 복잡한 필터를 구현하려면 에서 레포를 확장해야 합니다.QuerydslBinderCustomizer그리고 그것을 사용합니다.customize메소드(레포에 저장되어 있음).

예를 들어 다음에 대한 '사이' 필터를 구현할 수 있습니다.heightMeters및 '좋아요' 필터:surname:

public interface ProfileRepository extends JpaRepository<Profile, Long>, QuerydslPredicateExecutor<Profile>, QuerydslBinderCustomizer<QProfile> {

    @Override
    default void customize(QuerydslBindings bindings, QProfile profile) {

      bindings.excluding( // used to exclude unnecessary fields from the filter
          profile.id,
          profile.version,
          // ...
      );

      bindings.bind(profile.heightMeters).all((path, value) -> {

          Iterator<? extends BigDecimal> it = value.iterator();
          BigDecimal from = it.next();
          if (value.size() >= 2) {
              BigDecimal to = it.next();
              return path.between(from, to)); // between - if you specify heightMeters two times
          } else {
              return path.goe(from); // or greter than - if you specify heightMeters one time
          }
      });

      bindings.bind(profile.surname).first(StringExpression::containsIgnoreCase);        
    }
}

그런 다음 프로필을 쿼리할 수 있습니다.

/profiles?isMale=0&heightMeters=1.4&heightMeters=1.6&surename=doe

예를 들어 - 키가 1.4~1.6m이고 이름에 'surename'이 포함된 모든 여성을 찾습니다.

Spring Data REST를 사용하지 않는 경우 QueryDSL 지원을 통해 자체 Rest Controller 방법을 구현할 수 있습니다.

@RestController
@RequestMapping("/profiles")
public class ProfileController {

    @Autowired private ProfileRepository profileRepo;

    @GetMapping
    public ResponseEntity<?> getAll(@QuerydslPredicate(root = Profile.class, bindings = ProfileRepository.class) Predicate predicate, Pageable pageable) {

        Page<Profile> profiles = profileRepo.findAll(predicate, pageable);
        return ResponseEntity.ok(profiles);
    }
}

참고: 프로젝트에 QueryDSL 종속성을 추가하는 것을 잊지 마십시오.

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <scope>provided</scope>
</dependency>

<build>
    <plugins>
        <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.1.3</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/generated-sources/annotations</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>                                                       
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

그런 다음 프로젝트를 컴파일합니다(예:mvn compile) 'Q' 클래스를 만들 수 있도록 합니다.

답변은 매우 쉬우며 봄에 예제별 쿼리를 사용할 수 있습니다.

그리고 더 나아가 당신은 모든 것을 나열할 필요가 없습니다.Profile컨트롤러의 속성, 당신은 그냥.Profile매개 변수로서, 봄이 그것을 처리할 것입니다.

그리고 요청 매개 변수의 유효성을 검사하려면 "givenName"을 예로 들어 다음과 같이 보다 쉽게 통합할 수 있습니다.NotNull엔티티에서, 그리고 추가.@Valid컨트롤러에서 "givenName"이 요청 매개 변수에 없는 경우 "Bad Request" 응답을 받게 됩니다.

다음은 작동 코드입니다.

@Entity
@Table(name = "profiles")
public class Profile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "profile_given_name")
    @NotNull
    private String givenName;

    @Column(name = "profile_surname")
    private String surname;

    @Column(name = "profile_is_male")
    private Integer isMale;

    @Column(name = "profile_height_meters", columnDefinition = "DOUBLE")
    private BigDecimal heightMeters;

    @Column(name = "profile_weight_kilos", columnDefinition = "DOUBLE")
    private BigDecimal weightKilos;

    @Column(name = "profile_dob")
    private Date dob;
}

프로필 리소스

@RestController
@RequestMapping("/v1/profiles")
public class ProfileResource {
    @Autowired
    ProfileRepository profileRepository;

    @GetMapping
    public ResponseEntity<List<Profile>> searchProfiles(@Valid Profile profile) {
        List<Profile> all = profileRepository.findAll(Example.of(profile));
        return ResponseEntity.ok(all);
    }
}

프로필 리포지토리

public interface ProfileRepository extends JpaRepository<Profile, Long> {
}

그러면 다음을 보냅니다.GET /v1/profiles?isMale=0원하는 대로 HTTP 방식.

스프링 데이터에서 "예별 쿼리"를 확인하십시오.당신이 필요로 하는 것에 맞는 것 같습니다.

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/ #별로 표시

언급URL : https://stackoverflow.com/questions/48346341/spring-boot-jpa-implementing-search-queries-with-optional-ranged-criteria

반응형