Spring Boot & JPA: تنفيذ استعلامات البحث وفقًا لمعايير اختيارية تراوحت




spring-boot spring-data-jpa (3)

الإجابة حريصة جدًا ويمكنك استخدام query-by-example في فصل الربيع.

وحتى إذا لم تكن بحاجة إلى سرد جميع خصائص Profile في وحدة التحكم الخاصة بك ، فأنت تأخذ فقط Profile التعريف كمعلمة ، حيث سيتولى ربيعه.

وبما أنك تريد التحقق من صحة طلب البحث ، فيُمكنك التكامل مع أداة التحقق من صحة الفول ، واتخاذ "givenName" كمثال. إضافة NotNull في الكيان ، وإضافة @Valid في وحدة التحكم ، في حالة عدم وجود "givenName" في بارامترات الطلب ، سوف تحصل على استجابة "طلب غير صالح".

فيما يلي رموز العمل:

@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 كما تريد.

هذا هو 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 وأريد الكشف عن نقطة نهاية 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 ) ، فأنا متوقف عن كيفية كتابة استعلام JPQL في ملف ProfileRepository :

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

كيف يمكنني تطبيق @Query(...) لـ ProfileRepository#searchProfiles بطريقة تمكن جميع معايير البحث الخاصة بي (بالنظر إلى جميع النطاقات المسموح بها وقيم المعايير للبحث عنها) ، وتسمح لأي معايير أن تكون فارغة / اختيارية ؟

بالطبع ، إذا كانت هناك أي مكتبات صغيرة أو إذا كان Spring Boot / JPA لديه بالفعل حل لهذا ، فأنا آذان!


تحقق من "الاستعلام حسب المثال" في بيانات الربيع. يبدو أنه يناسب فاتورة ما تحتاجه ...

query-by-example


يمكنك تحقيق استعلامات معقدة بمواصفات JpaSpecificationExecutor في بيانات الربيع. يجب أن تقوم واجهة مستودع JpaSpecificationExecutor<T> بتوسيع واجهة 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 . تعرّف هذه الواجهة طرق إجراء البحث عبر فئة مواصفة.

ما يتعين علينا القيام به الآن هو تحديد PersonSpecification التي ستُرجع " Predicate يحتوي على قيود الاستعلام (في المثال ، يقوم PersonSpecification بتنفيذ الاستعلام ، حدد * من الشخص حيث name =؟ أو (title =؟ و age =؟) ):

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

Here مثال كامل موجود في جيثب

كما يمكنك إنشاء أي استعلامات معقدة باستخدام المواصفات





query-by-example