spring boot स्प्रिंग बूट और जेपीए: वैकल्पिक, बराबरी के मापदंड के साथ खोज प्रश्नों को लागू करना




spring-boot spring-data-jpa (4)

लगभग आपको जो कुछ भी चाहिए वह पहले से ही Querydsl और वेब समर्थन स्प्रिंग डेटा एक्सटेंशन की सहायता से स्प्रिंग डेटा में लागू है।

आपको अपने रेपो को QuerydslPredicateExecutor से विस्तारित करना चाहिए और, यदि आप स्प्रिंग डेटा QuerydslPredicateExecutor का उपयोग कर रहे हैं, तो आप अपने रेपो डेटा को 'बॉक्स से' आधार फ़िल्टरिंग, पेजिंग और सॉर्टिंग समर्थन के साथ क्वेरी कर सकते हैं:

/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.6 मीटर के बीच है और सर्नेम में 'डो' है।

यदि आप स्प्रिंग डेटा रीस्ट का उपयोग नहीं कर रहे हैं, तो आप 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 ) इसे 'क्यू' कक्षाएं बनाने दें।

यह एक SSCCE है , अनुसंधान से पता चलता है, एक डुबकी नहीं है और विषय पर है !!!

स्प्रिंग बूट रेस्ट सर्विस और 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 क्वेरी को ProfileRepository में ProfileRepository :

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

मैं @Query(...) ProfileRepository#searchProfiles के लिए इस तरह से @Query(...) को कैसे लागू कर सकता हूं जो मेरे सभी खोज मानदंडों (खोज के लिए सभी अनुमेय श्रेणियों और मानों को देखते हुए @Query(...) को सक्षम बनाता है, और किसी भी मापदंड को शून्य या वैकल्पिक होने देता है। ?

बेशक, अगर कोई निफ्टी थोड़ा पुस्तकालय है या अगर स्प्रिंग बूट / जेपीए के पास पहले से ही इसके लिए एक समाधान है, तो मैं सभी कान हूं!


इसका उत्तर काफी आसान है और आप query-by-example का उपयोग वसंत में कर सकते हैं।

और इससे भी अधिक आपको अपने नियंत्रक में सभी Profile गुणों को सूचीबद्ध करने की आवश्यकता नहीं है, आप बस Profile को पैरामीटर के रूप में लेते हैं, वसंत इसका ख्याल रखेगा।

और जैसा कि आप अनुरोध परमेस को मान्य करना चाहते हैं, यहां बीन सत्यापनकर्ता के साथ एकीकरण करने के लिए ईयर है, उदाहरण के रूप में "दिया गया नाम" लें। इकाई में @Valid जोड़ें, और नियंत्रक में @Valid जोड़ें, यदि "दिया गया" अनुरोध अनुरोधों में नहीं है, तो आपको "खराब अनुरोध" प्रतिक्रिया मिलेगी।

यहाँ काम कर रहे कोड हैं:

@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 भेजें GET /v1/profiles?isMale=0 कि आप चाहते हैं।


वसंत डेटा में "उदाहरण के द्वारा क्वेरी" देखें। लगता है कि आप क्या जरूरत के लिए बिल फिट ...

query-by-example


आप वसंत डेटा में 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 उस क्वेरी का चयन उस व्यक्ति से कर रहा है जहां नाम =? या (उपनाम =? और उम्र =?) ):

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