これは [〜#〜] 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が既にこのソリューションを持っている場合、私はすべて耳にします!
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にある完全な例です
また、仕様を使用して複雑なクエリを作成できます
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」クラスを作成します。
答えは非常に簡単で、春には 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メソッド。
春のデータの「例によるクエリ」をご覧ください。あなたが必要なものの法案に合うようです...
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example