web-dev-qa-db-ja.com

Spring BootおよびJPA:オプションの範囲付き基準を使用した検索クエリの実装

これは [〜#〜] sscce [〜#〜] であり、研究を示し、だまされたものではなく、話題になっています!!!


Spring Boot 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もあり、広範囲の基準に基づいてProfilesを検索するための非常に柔軟で堅牢な方法を提供するGETエンドポイントを公開します。

_# 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にJPQLクエリを記述する方法に固執しています。

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

どのように_ProfileRepository#searchProfiles_の@Query(...)を実装して、すべての検索基準を有効にすることができますか(すべての許容範囲と基準値を検索する場合) 、および任意の基準をnull /オプションにすることができますか?

もちろん、気の利いた小さなライブラリがある場合や、Spring Boot/JPAが既にこのソリューションを持っている場合、私はすべて耳にします!

9
smeeb

SpringデータのJpaSpecificationExecutorによる仕様で複雑なクエリを実現できます。リポジトリインターフェイスはJpaSpecificationExecutor<T>インターフェイスを拡張して、新しいSpecification<T>オブジェクトを作成してデータベースクエリの条件を指定できるようにする必要があります。

トリックは、仕様インターフェイスを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 * from person where name =?または(姓=?および年齢=?)):

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にある完全な例です

また、仕様を使用して複雑なクエリを作成できます

17
Bhushan Uniyal

Querydsl および Web support Spring Data拡張機能の助けを借りて、Spring Dataに必要なものは既に実装されています。

リポジトリもQuerydslPredicateExecutorから拡張する必要があり、 Spring Data REST を使用している場合は、ベースフィルタリング、ページング、ソートを使用して、「ボックスから」直接リポジトリデータをクエリできます。サポート:

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

より複雑なフィルターを実装するには、QuerydslBinderCustomizerからリポジトリを拡張し、そのcustomizeメソッドを使用する必要があります(リポジトリ内で直接)。

たとえば、heightMetersには「between」フィルターを、surnameには「like」フィルターを実装できます。

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.6メートルで、surenameに「doe」が含まれるすべての女性を見つけます。

Spring Data RESTを使用していない場合は、QueryDSLサポートを使用して独自のレストコントローラーメソッドを実装できます。

@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」クラスを作成します。

4
Cepr0

答えは非常に簡単で、春には query-by-example を使用できます。

さらに、コントローラーのすべてのProfileプロパティをリストする必要はありません。パラメーターとしてProfileを使用するだけで、springが処理します。

そして、リクエストパラメータを検証したいので、ここではbeanバリデータと統合する方が簡単です。例として "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;
}

ProfileResource

@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);
    }
}

ProfileRepository

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

その後、GET /v1/profiles?isMale=0必要なHTTPメソッド。

3
Liping Huang

春のデータの「例によるクエリ」をご覧ください。あなたが必要なものの法案に合うようです...

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example

0
LetsBeFrank