From e82a84a66722ae4e51eb7fbe157300a6190cdab1 Mon Sep 17 00:00:00 2001 From: Hao Wen Date: Mon, 19 Mar 2018 13:22:15 +0800 Subject: [PATCH] [Wen Hao] - using guava Range class instead of Spring Data JPA Range class. --- README.md | 28 +- README_CN.md | 20 +- build.gradle | 11 +- docs/3.2.2.md | 363 ++++++++++++++ docs/3.2.2_cn.md | 473 ++++++++++++++++++ gradle.properties | 3 +- .../github/wenhao/jpa/PredicateBuilder.java | 6 +- .../specification/BetweenSpecification.java | 11 +- .../wenhao/jpa/integration/BetweenTest.java | 21 +- .../wenhao/jpa/integration/JoinTest.java | 4 +- .../github/wenhao/jpa/integration/OrTest.java | 4 +- .../wenhao/jpa/integration/PredicateTest.java | 27 +- 12 files changed, 901 insertions(+), 70 deletions(-) create mode 100644 docs/3.2.2.md create mode 100644 docs/3.2.2_cn.md diff --git a/README.md b/README.md index 76e31b3..9ce537b 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ English Version: [Latest] +[3.2.2] + [3.2.1] [3.1.0] @@ -31,6 +33,8 @@ Chinese Version: [最新] +[3.2.2_cn] + [3.2.1_cn] [3.1.0_cn] @@ -45,7 +49,7 @@ repositories { } dependencies { - compile 'com.github.wenhao:jpa-spec:3.2.1' + compile 'com.github.wenhao:jpa-spec:3.2.2' } ``` @@ -55,7 +59,7 @@ dependencies { com.github.wenhao jpa-spec - 3.2.1 + 3.2.2 ``` @@ -81,7 +85,7 @@ public Page findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt(Objects.nonNull(request.getAge()), "age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%", "%me") .build(); @@ -150,8 +154,8 @@ find any person age between 18 and 25, birthday between someday and someday. ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between(Objects.nonNull(request.getAge(), "age", Range.of(inclusive(18), inclusive(25))) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between(Objects.nonNull(request.getAge(), "age", Range.closed(18, 25)) + .between("birthday", Range.closed(new Date(), new Date())) .build(); return personRepository.findAll(specification); @@ -236,7 +240,7 @@ public List findAll(SearchRequest request) { ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) + .between("age", Range.closed(10, 35)) .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") .build(); @@ -273,7 +277,7 @@ public List findAll(SearchRequest request) { ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) + .between("age", Range.closed(10, 35)) .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { Join address = root.join("addresses", JoinType.LEFT); return cb.equal(address.get("street"), "Chengdu"); @@ -293,7 +297,7 @@ public List findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt("age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%") .build(); @@ -315,7 +319,7 @@ public Page findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt("age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%") .build(); @@ -379,11 +383,13 @@ Copyright © 2016-2018 Wen Hao Licensed under [Apache License] -[Latest]: ./docs/3.2.1.md +[Latest]: ./docs/3.2.2.md +[3.2.2]: ./docs/3.2.2.md [3.2.1]: ./docs/3.2.1.md [3.1.0]: ./docs/3.1.0.md [3.0.0]: ./docs/3.0.0.md -[最新]: ./docs/3.2.1_cn.md +[最新]: ./docs/3.2.2_cn.md +[3.2.2_cn]: ./docs/3.2.2_cn.md [3.2.1_cn]: ./docs/3.2.1_cn.md [3.1.0_cn]: ./docs/3.1.0_cn.md [3.0.0_cn]: ./docs/3.0.0_cn.md diff --git a/README_CN.md b/README_CN.md index 0c4038e..bf51ee7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -39,7 +39,7 @@ repositories { } dependencies { - compile 'com.github.wenhao:jpa-spec:3.2.1' + compile 'com.github.wenhao:jpa-spec:3.2.2' } ``` @@ -49,7 +49,7 @@ dependencies { com.github.wenhao jpa-spec - 3.2.1 + 3.2.2 ``` @@ -59,7 +59,7 @@ dependencies { com.github.wenhao jpa-spec - 3.2.1 + 3.2.2 org.hibernate.javax.persistence @@ -116,7 +116,7 @@ public Page findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt(Objects.nonNull(request.getAge()), "age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%", "%me") .build(); @@ -212,8 +212,8 @@ find any person age between 18 and 25, birthday between someday and someday. ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between(Objects.nonNull(request.getAge(), "age", Range.of(inclusive(18), inclusive(25))) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between(Objects.nonNull(request.getAge(), "age", Range.closed(18, 25)) + .between("birthday", Range.closed(new Date(), new Date())) .build(); return personRepository.findAll(specification); @@ -330,7 +330,7 @@ public List findAll(SearchRequest request) { ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) + .between("age", Range.closed(10, 35)) .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") .build(); @@ -379,7 +379,7 @@ public List findAll(SearchRequest request) { ```java public List findAll(SearchRequest request) { Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) + .between("age", Range.closed(10, 35)) .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { Join address = root.join("addresses", JoinType.LEFT); return cb.equal(address.get("street"), "Chengdu"); @@ -402,7 +402,7 @@ public List findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt("age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%") .build(); @@ -430,7 +430,7 @@ public Page findAll(SearchRequest request) { Specification specification = Specifications.and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt("age", 18) - .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between("birthday", Range.closed(new Date(), new Date())) .like("nickName", "%og%") .build(); diff --git a/build.gradle b/build.gradle index 64ae4c9..38eae04 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { } group = 'com.github.wenhao' -version = '3.2.1' +version = '3.2.2' idea { project { @@ -45,6 +45,7 @@ compileTestJava(compatibility) dependencies { compile("org.springframework.data:spring-data-jpa:$springDataJpaVersion") compile("org.hibernate.javax.persistence:hibernate-jpa-2.1-api:$jpaVersion") + compile("com.google.guava:guava:$guavaVersion") testCompile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion") testCompile("org.springframework.boot:spring-boot-starter-test:$springBootVersion") @@ -119,7 +120,7 @@ publishing { artifact javadocJar groupId 'com.github.wenhao' artifactId 'jpa-spec' - version '3.2.1' + version '3.2.2' pom.withXml { def root = asNode() root.appendNode('description', 'A JAP Query By Specification framework.') @@ -154,10 +155,10 @@ bintray { githubRepo = 'wenhao/jpa-spec' githubReleaseNotesFile = 'README.md' version { - name = '3.2.1' - desc = 'A JAP Query By Specification framework 3.2.1' + name = '3.2.2' + desc = 'A JAP Query By Specification framework 3.2.2' released = new Date() - vcsTag = '3.2.1' + vcsTag = '3.2.2' gpg { sign = true passphrase = 'passphrase' diff --git a/docs/3.2.2.md b/docs/3.2.2.md new file mode 100644 index 0000000..e163f0f --- /dev/null +++ b/docs/3.2.2.md @@ -0,0 +1,363 @@ +### Changes + +1. Fix bug, Spring Data JPA Range class require value must not be null, using guava Range instead. + +### Gradle + +```groovy +repositories { + jcenter() +} + +dependencies { + compile 'com.github.wenhao:jpa-spec:3.2.2' +} +``` + +### Maven + +```xml + + com.github.wenhao + jpa-spec + 3.2.2 + +``` + + +### Maven exclude dependencies + +```xml + + com.github.wenhao + jpa-spec + 3.2.2 + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.data + spring-data-jpa + + + +``` + +### Specification By Examples: + +#### Each specification support three parameters: + +1. **condition**: if true(default), apply this specification. +2. **property**: field name. +3. **values**: compare value with model, eq/ne/like support multiple values. + +#### General Example + +each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. + +```java +public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { +} +``` + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt(Objects.nonNull(request.getAge()), "age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%", "%me") + .build(); + + return personRepository.findAll(specification, new PageRequest(0, 15)); +} +``` + +#### Equal/NotEqual Example + +find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. + +**Test:** [EqualTest.java] and [NotEqualTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq("nickName", "dog") + .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) + .eq("company", null) //or eq("company", (Object) null) + .build(); + + return personRepository.findAll(specification); +} +``` + +#### In/NotIn Example + +find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". + +**Test:** [InTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") + .notIn("company", "ThoughtWorks", "IBM") + .build(); + + return personRepository.findAll(specification); +} +``` + +#### Comparison Example + +Support any comparison class which implements Comparable interface, find any people age bigger than 18. + +**Test:** [GtTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .gt(Objects.nonNull(request.getAge()), "age", 18) + .lt("birthday", new Date()) + .build(); + + return personRepository.findAll(specification); +} +``` + +#### Between Example + +find any person age between 18 and 25, birthday between someday and someday. + +**Test:** [BetweenTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between(Objects.nonNull(request.getAge(), "age", Range.closed(18, 25)) + .between("birthday", Range.closed(new Date(), new Date())) + .build(); + + return personRepository.findAll(specification); +} +``` + +#### Like/NotLike Example + +find any person name like %ac% or %og%, company not like %ec%. + +**Test:** [LikeTest.java] and [NotLikeTest.java] + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .like("name", "ac", "%og%") + .notLike("company", "ec") + .build(); + + return personRepository.findAll(specification); +} +``` + +#### Or + +support or specifications. + +**Test:** [OrTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.or() + .like("name", "%ac%") + .gt("age", 19) + .build(); + + return phoneRepository.findAll(specification); +} +``` + +#### Join + +each specification support association query as left join. + +**Test:** [JoinTest.java] + +@ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") + .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") + .build(); + + return phoneRepository.findAll(specification); +} +``` + +@ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between("age", Range.closed(10, 35)) + .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") + .build(); + + return phoneRepository.findAll(specification); +} +``` + +#### Custom Specification + +You can custom specification to do the @ManyToOne and @ManyToMany as well. + +@ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". + +**Test:** [AndTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") + .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { + Path person = root.get("person"); + return cb.equal(person.get("name"), "Jack"); + }) + .build(); + + return phoneRepository.findAll(specification); +} +``` + +@ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. + +**Test:** [AndTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between("age", Range.closed(10, 35)) + .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { + Join address = root.join("addresses", JoinType.LEFT); + return cb.equal(address.get("street"), "Chengdu"); + })) + .build(); + + return phoneRepository.findAll(specification); +} +``` + +#### Sort + +**Test:** [SortTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt("age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%") + .build(); + + Sort sort = Sorts.builder() + .desc(StringUtils.isNotBlank(request.getName()), "name") + .asc("birthday") + .build(); + + return personRepository.findAll(specification, sort); +} +``` + +#### Pagination + +find person by pagination and sort by name desc and birthday asc. + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt("age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%") + .build(); + + Sort sort = Sorts.builder() + .desc(StringUtils.isNotBlank(request.getName()), "name") + .asc("birthday") + .build(); + + return personRepository.findAll(specification, new PageRequest(0, 15, sort)); +} +``` + +#### Virtual View + +Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. + +There is no difference between a view and a database table for a Hibernate mapping. + +**Test:** [VirtualViewTest.java] + +```java +@Entity +@Immutable +@Subselect("SELECT p.id, p.name, p.age, ic.number " + + "FROM person p " + + "LEFT JOIN id_card ic " + + "ON p.id_card_id=ic.id") +public class PersonIdCard { + @Id + private Long id; + private String name; + private Integer age; + private String number; + + // Getters and setters are omitted for brevity +} +``` + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .gt(Objects.nonNull(request.getAge()), "age", 18) + .build(); + + return personIdCardRepository.findAll(specification); +} +``` + +#### Projection, GroupBy, Aggregation + +Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, + +furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. + +Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. + +### Copyright and license + +Copyright © 2016-2018 Wen Hao + +Licensed under [Apache License] + +[EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java +[NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java +[InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java +[GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java +[BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java +[LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java +[NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java +[OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java +[AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java +[JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java +[SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java +[VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java +[Apache License]: ./LICENSE diff --git a/docs/3.2.2_cn.md b/docs/3.2.2_cn.md new file mode 100644 index 0000000..1affe93 --- /dev/null +++ b/docs/3.2.2_cn.md @@ -0,0 +1,473 @@ +### Changes + +1. Bug修复,Spring Data JPA的Range类要求值不能为空NULL,换用guava的Range类。 + +### Gradle + +```groovy +repositories { + jcenter() +} + +dependencies { + compile 'com.github.wenhao:jpa-spec:3.2.2' +} +``` + +### Maven + +```xml + + com.github.wenhao + jpa-spec + 3.2.2 + +``` + +### Maven排除项目已存在的依赖 + +```xml + + com.github.wenhao + jpa-spec + 3.2.2 + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.data + spring-data-jpa + + + +``` + + +### 条件查询例子 + + +#### 每个条件查询支持三个参数: + + +1. **condition**: 如果为`true`(默认),应用此条件查询。 +2. **property**: 字段名称。 +3. **values**: 具体查询的值,eq/ne/like 支持多个值。 + + +#### 例子 + + +每个 Repository 类需要继承两个类 **JpaRepository** 和 **JpaSpecificationExecutor**。 + +```java +public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { +} +``` + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt(Objects.nonNull(request.getAge()), "age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%", "%me") + .build(); + + return personRepository.findAll(specification, new PageRequest(0, 15)); +} +``` + + +#### Equal/NotEqual例子 + + +查询任何昵称等于 "dog",名字等于 "Jack"/"Eric"或为null并且公司也为null的人。 + + +**Test:** [EqualTest.java] 和 [NotEqualTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq("nickName", "dog") + .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) + .eq("company", null) //or eq("company", (Object) null) + .build(); + + return personRepository.findAll(specification); +} +``` + + +#### In/NotIn例子 + + +查询任何名字等于 "Jack" 或 "Eric" 并且公司不等于 "ThoughtWorks" 或 "IBM" 的人。 + +**Test:** [InTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") + .notIn("company", "ThoughtWorks", "IBM") + .build(); + + return personRepository.findAll(specification); +} +``` + + +#### 比较例子 + + +支持任何实现Comparable接口的类的比较,查询任何年纪大于等于18的人。 + +**Test:** [GtTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .gt(Objects.nonNull(request.getAge()), "age", 18) + .lt("birthday", new Date()) + .build(); + + return personRepository.findAll(specification); +} +``` + + +#### Between例子 + + +查询任何年龄在18到25,生日在某个时间段的人。 + +**Test:** [BetweenTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between(Objects.nonNull(request.getAge(), "age", Range.closed(18, 25)) + .between("birthday", Range.closed(new Date(), new Date())) + .build(); + + return personRepository.findAll(specification); +} +``` + + +#### Like/NotLike例子 + + +查询任何名字包含 %ac% 或 %og%,公司不包含 %ec% 的人。 + + +**Test:** [LikeTest.java] 和 [NotLikeTest.java] + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .like("name", "ac", "%og%") + .notLike("company", "ec") + .build(); + + return personRepository.findAll(specification); +} +``` + + +#### Or例子 + + +支持或条件查询。 + +**Test:** [OrTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.or() + .like("name", "%ac%") + .gt("age", 19) + .build(); + + return phoneRepository.findAll(specification); +} +``` + + +#### 关联查询 + + +每个条件查询都支持左连接查询。 + +**Test:** [JoinTest.java] + + +多对一查询,查询任何名字等于 "Jack" 并且此人的电话品牌是 "HuaWei"的人。 + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") + .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") + .build(); + + return phoneRepository.findAll(specification); +} +``` + + +多对多查询,查询任何年龄在10到35之间并且其地址在 "Chengdu" 的人。 + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between("age", Range.closed(10, 35)) + .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") + .build(); + + return phoneRepository.findAll(specification); +} +``` + + +#### 自定义条件查询 + + +你也可以自定义条件查询来实现多对一和多对多查询。 + + +多对一查询,查询任何名字等于 "Jack" 并且此人的电话品牌是 "HuaWei"的人。 + +**Test:** [AndTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") + .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { + Path person = root.get("person"); + return cb.equal(person.get("name"), "Jack"); + }) + .build(); + + return phoneRepository.findAll(specification); +} +``` + + +多对多查询,查询任何年龄在10到35之间并且其地址在 "Chengdu" 的人。 + +**Test:** [AndTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .between("age", Range.closed(10, 35)) + .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { + Join address = root.join("addresses", JoinType.LEFT); + return cb.equal(address.get("street"), "Chengdu"); + })) + .build(); + + return phoneRepository.findAll(specification); +} +``` + + +#### 排序 + +**Test:** [SortTest.java] + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt("age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%") + .build(); + + Sort sort = Sorts.builder() + .desc(StringUtils.isNotBlank(request.getName()), "name") + .asc("birthday") + .build(); + + return personRepository.findAll(specification, sort); +} +``` + + +#### 分页 + + +分页并按照名字倒序生日升序查询。 + +```java +public Page findAll(SearchRequest request) { + Specification specification = Specifications.and() + .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) + .gt("age", 18) + .between("birthday", Range.closed(new Date(), new Date())) + .like("nickName", "%og%") + .build(); + + Sort sort = Sorts.builder() + .desc(StringUtils.isNotBlank(request.getName()), "name") + .asc("birthday") + .build(); + + return personRepository.findAll(specification, new PageRequest(0, 15, sort)); +} +``` + + +#### 虚拟视图 + + +如果你不想使用数据库视图(数据库依赖),可以 **@org.hibernate.annotations.Subselect** 虚拟视图代替(灵活修改/提升可读性)。 + + +对于 Hibernate 映射来说虚拟视图和数据库视图没任何区别。 + +**Test:** [VirtualViewTest.java] + +```java +@Entity +@Immutable +@Subselect("SELECT p.id, p.name, p.age, ic.number " + + "FROM person p " + + "LEFT JOIN id_card ic " + + "ON p.id_card_id=ic.id") +public class PersonIdCard { + @Id + private Long id; + private String name; + private Integer age; + private String number; + + // Getters and setters are omitted for brevity +} +``` + +```java +public List findAll(SearchRequest request) { + Specification specification = Specifications.and() + .gt(Objects.nonNull(request.getAge()), "age", 18) + .build(); + + return personIdCardRepository.findAll(specification); +} +``` + + +#### 投射、分组和聚合 + + +Spring Data JPA对投射、分组和聚合支持不是很好, + +此外,投射、分组和聚合支大多数用在比较复杂的统计报表或性能要求比较高的查询,如果使用 Hibernate/JPA 来对象关系映射来解决可能有点过于复杂了。 + +或者,使用虚拟视图并给一个易读的、有意义的类名来解决特定的问题也许是一个不错的选择。 + +### Copyright and license + +Copyright © 2016-2018 Wen Hao + +Licensed under [Apache License] + +[EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java +[NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java +[InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java +[GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java +[BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java +[LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java +[NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java +[OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java +[AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java +[JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java +[SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java +[VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java +[Apache License]: ./LICENSE diff --git a/gradle.properties b/gradle.properties index 17b5c00..a607b0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ springDataJpaVersion=2.0.5.RELEASE springBootVersion=2.0.0.RELEASE jpaVersion=1.0.2.Final +guavaVersion=24.1-jre ## testCompile springOrmVersion=5.0.4.RELEASE @@ -14,4 +15,4 @@ bintrayUser=wenhao bintrayApiKey=b081f7492fb1970bd47af32c339c1fcf0829dae6 sonatypeUsername=wenhao -sonatypePassword= +sonatypePassword=1qaz!QAZ diff --git a/src/main/java/com/github/wenhao/jpa/PredicateBuilder.java b/src/main/java/com/github/wenhao/jpa/PredicateBuilder.java index 610c018..c44e1ed 100644 --- a/src/main/java/com/github/wenhao/jpa/PredicateBuilder.java +++ b/src/main/java/com/github/wenhao/jpa/PredicateBuilder.java @@ -11,7 +11,7 @@ import com.github.wenhao.jpa.specification.NotEqualSpecification; import com.github.wenhao.jpa.specification.NotInSpecification; import com.github.wenhao.jpa.specification.NotLikeSpecification; -import org.springframework.data.domain.Range; +import com.google.common.collect.Range; import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.CriteriaBuilder; @@ -83,11 +83,11 @@ public PredicateBuilder le(boolean condition, String property, Comparable return this.predicate(condition, new LeSpecification(property, compare)); } - public PredicateBuilder between(String property, Range> range) { + public PredicateBuilder between(String property, Range range) { return between(true, property, range); } - public PredicateBuilder between(boolean condition, String property, Range> range) { + public PredicateBuilder between(boolean condition, String property, Range range) { return this.predicate(condition, new BetweenSpecification(property, range)); } diff --git a/src/main/java/com/github/wenhao/jpa/specification/BetweenSpecification.java b/src/main/java/com/github/wenhao/jpa/specification/BetweenSpecification.java index 724b47c..ca5f9f0 100644 --- a/src/main/java/com/github/wenhao/jpa/specification/BetweenSpecification.java +++ b/src/main/java/com/github/wenhao/jpa/specification/BetweenSpecification.java @@ -1,6 +1,7 @@ package com.github.wenhao.jpa.specification; -import org.springframework.data.domain.Range; + +import com.google.common.collect.Range; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -11,9 +12,9 @@ public class BetweenSpecification extends AbstractSpecification { private final String property; - private final Range range; + private final Range range; - public BetweenSpecification(String property, Range range) { + public BetweenSpecification(String property, Range range) { this.property = property; this.range = range; } @@ -22,8 +23,6 @@ public BetweenSpecification(String property, Range range) { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { From from = getRoot(property, root); String field = getProperty(property); - Comparable lower = (Comparable) range.getLowerBound().getValue().get(); - Comparable upper = (Comparable) range.getUpperBound().getValue().get(); - return cb.between(from.get(field), lower, upper); + return cb.between(from.get(field), range.lowerEndpoint(), range.upperEndpoint()); } } diff --git a/src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java b/src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java index 83c5cbd..4f2a2eb 100644 --- a/src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java +++ b/src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java @@ -4,12 +4,11 @@ import com.github.wenhao.jpa.builder.PersonBuilder; import com.github.wenhao.jpa.model.Person; import com.github.wenhao.jpa.repository.PersonRepository; +import com.google.common.collect.Range; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.Range; -import org.springframework.data.domain.Range.Bound; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; @@ -18,8 +17,8 @@ import java.util.Date; import java.util.List; +import static java.util.Objects.nonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.domain.Range.Bound.inclusive; @RunWith(SpringRunner.class) @DataJpaTest @@ -32,20 +31,20 @@ public class BetweenTest { public void should_be_able_to_find_by_using_between() throws ParseException { // given Person jack = new PersonBuilder() - .name("Jack") - .birthday(getDate("1987-11-14")) - .build(); + .name("Jack") + .birthday(getDate("1987-11-14")) + .build(); Person eric = new PersonBuilder() - .name("Eric") - .birthday(getDate("1990-10-12")) - .build(); + .name("Eric") + .birthday(getDate("1990-10-12")) + .build(); personRepository.save(jack); personRepository.save(eric); // when Specification specification = Specifications.and() - .between(jack.getBirthday() != null, "birthday", Range.of(inclusive(getDate("1980-01-01")), inclusive(getDate("1989-12-31")))) - .build(); + .between(nonNull(jack.getBirthday()), "birthday", Range.closed(getDate("1980-01-01"), getDate("1989-12-31"))) + .build(); List persons = personRepository.findAll(specification); diff --git a/src/test/java/com/github/wenhao/jpa/integration/JoinTest.java b/src/test/java/com/github/wenhao/jpa/integration/JoinTest.java index 9f3f257..43a1e05 100644 --- a/src/test/java/com/github/wenhao/jpa/integration/JoinTest.java +++ b/src/test/java/com/github/wenhao/jpa/integration/JoinTest.java @@ -6,12 +6,12 @@ import com.github.wenhao.jpa.model.Phone; import com.github.wenhao.jpa.repository.PersonRepository; import com.github.wenhao.jpa.repository.PhoneRepository; +import com.google.common.collect.Range; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.Range; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; @@ -93,7 +93,7 @@ public void should_be_able_to_find_by_using_many_to_many_query() { // when Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) + .between("age", Range.closed(10, 35)) .eq("addresses.street", "Chengdu") .build(); diff --git a/src/test/java/com/github/wenhao/jpa/integration/OrTest.java b/src/test/java/com/github/wenhao/jpa/integration/OrTest.java index f7b4360..56d4e80 100644 --- a/src/test/java/com/github/wenhao/jpa/integration/OrTest.java +++ b/src/test/java/com/github/wenhao/jpa/integration/OrTest.java @@ -4,11 +4,11 @@ import com.github.wenhao.jpa.builder.PersonBuilder; import com.github.wenhao.jpa.model.Person; import com.github.wenhao.jpa.repository.PersonRepository; +import com.google.common.collect.Range; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.Range; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; @@ -52,7 +52,7 @@ public void should_be_able_to_find_by_using_or_with_multiple_values() { .gt("age", 19) .eq(jack.getCompany() != null, null) .ne(jack.getNickName() != null, null) - .between(jack.getBirthday() != null, "birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) + .between(jack.getBirthday() != null, "birthday", Range.closed(new Date(), new Date())) .build(); List persons = personRepository.findAll(specification); diff --git a/src/test/java/com/github/wenhao/jpa/integration/PredicateTest.java b/src/test/java/com/github/wenhao/jpa/integration/PredicateTest.java index 924ddb2..cf5775a 100644 --- a/src/test/java/com/github/wenhao/jpa/integration/PredicateTest.java +++ b/src/test/java/com/github/wenhao/jpa/integration/PredicateTest.java @@ -6,27 +6,22 @@ import com.github.wenhao.jpa.model.Phone; import com.github.wenhao.jpa.repository.PersonRepository; import com.github.wenhao.jpa.repository.PhoneRepository; +import com.google.common.collect.Range; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.Range; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.domain.Range.Bound.inclusive; @RunWith(SpringRunner.class) @DataJpaTest @@ -59,12 +54,9 @@ public void should_be_able_to_find_by_using_many_to_one_query() { // when Specification specification = Specifications.and() .eq("brand", "HuaWei") - .predicate(StringUtils.isNotBlank(jack.getName()), new Specification() { - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - Path person = root.get("person"); - return cb.equal(person.get("name"), jack.getName()); - } + .predicate(StringUtils.isNotBlank(jack.getName()), (Specification) (root, query, cb) -> { + Path person = root.get("person"); + return cb.equal(person.get("name"), jack.getName()); }) .build(); @@ -107,13 +99,10 @@ public void should_be_able_to_find_by_using_many_to_many_query() { // when Specification specification = Specifications.and() - .between("age", Range.of(inclusive(10), inclusive(35))) - .predicate(StringUtils.isNotBlank(jack.getName()), new Specification() { - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - Join address = root.join("addresses", JoinType.LEFT); - return cb.equal(address.get("street"), "Chengdu"); - } + .between("age", Range.closed(10, 35)) + .predicate(StringUtils.isNotBlank(jack.getName()), (Specification) (root, query, cb) -> { + Join address = root.join("addresses", JoinType.LEFT); + return cb.equal(address.get("street"), "Chengdu"); }) .build();