├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── README_CN.md ├── _config.yml ├── build.gradle ├── config ├── checkstyle │ └── checkstyle.xml ├── copyright │ └── copyright.header └── githooks │ ├── commit-msg │ └── pre-push ├── docs ├── 3.0.0.md ├── 3.0.0_cn.md ├── 3.1.0.md ├── 3.1.0_cn.md ├── 3.1.1.md ├── 3.1.1_cn.md ├── 3.2.1.md ├── 3.2.1_cn.md ├── 3.2.4.md └── 3.2.4_cn.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── com │ └── github │ └── wenhao │ └── jpa │ ├── PredicateBuilder.java │ ├── Sorts.java │ ├── Specifications.java │ └── specification │ ├── AbstractSpecification.java │ ├── BetweenSpecification.java │ ├── EqualSpecification.java │ ├── GeSpecification.java │ ├── GtSpecification.java │ ├── InSpecification.java │ ├── LeSpecification.java │ ├── LikeSpecification.java │ ├── LtSpecification.java │ ├── NotEqualSpecification.java │ ├── NotInSpecification.java │ └── NotLikeSpecification.java └── test ├── java └── com │ └── github │ └── wenhao │ └── jpa │ ├── Application.java │ ├── builder │ └── PersonBuilder.java │ ├── integration │ ├── AndOrTest.java │ ├── BetweenTest.java │ ├── EqualTest.java │ ├── GreatEqualTest.java │ ├── GreatThanTest.java │ ├── InTest.java │ ├── JoinTest.java │ ├── LessEqualTest.java │ ├── LessThanTest.java │ ├── LikeTest.java │ ├── NotEqualTest.java │ ├── NotInTest.java │ ├── NotLikeTest.java │ ├── OrTest.java │ ├── PredicateTest.java │ ├── SortsTest.java │ └── VirtualViewTest.java │ ├── model │ ├── Address.java │ ├── IdCard.java │ ├── Person.java │ ├── PersonIdCard.java │ └── Phone.java │ └── repository │ ├── PersonIdCardRepository.java │ ├── PersonRepository.java │ └── PhoneRepository.java └── resources └── application.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | #*.jar 5 | *.war 6 | *.ear 7 | 8 | .DS_Store 9 | .gradle/ 10 | .idea/ 11 | build/ 12 | *.ipr 13 | *.iws 14 | *.iml 15 | src/.DS_Store 16 | 17 | .java-version 18 | 19 | out -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | addons: 6 | sonarcloud: 7 | organization: "wenhao" 8 | token: 9 | secure: "oOcew9ej+5mAzwX0kY1bTNHHOYePdtwBTS6sgtktxlLOi+O7Cjwo9OQV+RrZjqwsgNWuQ/tzNk8Ri4XLz/WNJgFxLWridUc8K/7lJswuSEmPKTHE/rSg0TZwhgZin1SOLqq5gkhb2o54QJZS/rdzjqdBJ2+fR3x5tNLcHs368EVy6Cr/Wunm4o/MolknthrXJGR9A31+zSgLD+JjCIgIfpk8kBOedFudIPmUK/QHR0NgZsw/Vr4Zv+xDDxGQZl1rK8gazrOiDpumwO+CxFPdCeuvH/RimhEr6W9TSCA78DCKDE+s8mrJnDR2XdutDGRVARegwnfjR9qzfECEaNqFQUJUlvwPyQ9gpv/4qA4iH1xTpIlFTgYCOcsvX0z/Ci7kMNEWwgNwAp91MHIAtBedUPixfSwU8IbJNb2DZfbGKCPxtNmyhXbBvXlVc/WkYoTWVfvmAehHCiyGUnMvc4LpER/dBbmqpcBXSw2zuEGH8QlPMel9kvfWvH1AA7cehjgZodOFGwLLAVFrf+D8x2DgQQz84CAYD2lgulLB+gK/sgrNTB1VIzv7NXEPJDcFgHg0OZQuRSAr3zyjBjzDVXEIYKsxbuAsaEADqH2yRt9loOeUjKcuhOQFWBmbmx4nD5XevBHKA0rdFOhOiKWESfwxjYYulgTzwxsTesUJ3zcQU68=" 10 | 11 | cache: 12 | directories: 13 | - '$HOME/.m2/repository' 14 | - '$HOME/.sonar/cache' 15 | - '$HOME/.gradle' 16 | - '.gradle' 17 | 18 | install: 19 | - TERM=dumb ./gradlew assemble 20 | 21 | script: 22 | - TERM=dumb ./gradlew check sonarqube -i 23 | 24 | after_success: 25 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wenhao@126.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2019, Wen Hao . 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.jfrog.bintray" version "1.8.4" 3 | id "org.owasp.dependencycheck" version "5.2.1" 4 | id 'checkstyle' 5 | id 'java-library' 6 | id 'idea' 7 | id 'net.ltgt.apt' version '0.21' 8 | id 'maven' 9 | id 'maven-publish' 10 | id 'signing' 11 | id 'jacoco' 12 | id "com.github.ksoichiro.spelling" version "0.1.1" 13 | id "com.simonharrer.modernizer" version "1.6.0-1" 14 | id 'com.github.ksoichiro.console.reporter' version '0.6.2' 15 | id 'com.github.ksoichiro.build.info' version '0.2.0' 16 | id "com.github.ben-manes.versions" version '0.22.0' 17 | id "net.rdrei.android.buildtimetracker" version "0.11.0" 18 | id "com.github.hierynomus.license" version "0.15.0" 19 | id "org.sonarqube" version "2.7.1" 20 | } 21 | 22 | repositories { 23 | jcenter() 24 | } 25 | 26 | group = 'com.github.wenhao' 27 | version = '4.0.0-SNAPSHOT' 28 | 29 | idea { 30 | project { 31 | vcs = 'Git' 32 | } 33 | module { 34 | jdkName = '1.8' 35 | } 36 | } 37 | 38 | dependencies { 39 | compileOnly "org.springframework.data:spring-data-jpa:$springDataJpaVersion" 40 | compileOnly "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:$jpaVersion" 41 | 42 | testImplementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" 43 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 44 | testImplementation "org.apache.commons:commons-lang3:$commonsLang3Version" 45 | testImplementation "com.h2database:h2:$h2Version" 46 | testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" 47 | testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" 48 | testImplementation "org.assertj:assertj-core:$assertjVersion" 49 | testImplementation "org.mockito:mockito-core:$mockitoVersion" 50 | testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" 51 | 52 | task sourcesJar(type: Jar) { 53 | from sourceSets.main.allSource 54 | classifier = 'sources' 55 | } 56 | 57 | task javadocJar(type: Jar, dependsOn: javadoc) { 58 | classifier = 'javadoc' 59 | from javadoc.destinationDir 60 | } 61 | 62 | artifacts { 63 | archives jar 64 | archives sourcesJar 65 | archives javadocJar 66 | } 67 | 68 | ext.isReleaseVersion = !version.endsWith("SNAPSHOT") 69 | 70 | signing { 71 | required { isReleaseVersion && gradle.taskGraph.hasTask("uploadArchives") } 72 | sign configurations.archives 73 | } 74 | } 75 | 76 | test { 77 | testLogging { 78 | events 'PASSED', 'FAILED', 'SKIPPED' 79 | } 80 | useJUnitPlatform() 81 | } 82 | 83 | checkstyle { 84 | toolVersion '8.1' 85 | ignoreFailures = false 86 | configFile file("$project.rootDir/config/checkstyle/checkstyle.xml") 87 | sourceSets = [sourceSets.main] 88 | 89 | checkstyleMain { 90 | source = 'src/main/java' 91 | } 92 | } 93 | 94 | jacocoTestReport { 95 | reports { 96 | xml.enabled true 97 | html.enabled true 98 | csv.enabled false 99 | } 100 | } 101 | 102 | jacocoTestCoverageVerification { 103 | violationRules { 104 | rule { 105 | limit { 106 | minimum = 0.8 107 | } 108 | } 109 | 110 | rule { 111 | enabled = true 112 | element = 'PACKAGE' 113 | includes = ['com.*'] 114 | 115 | limit { 116 | counter = 'LINE' 117 | value = 'COVEREDRATIO' 118 | minimum = 0.8 119 | } 120 | limit { 121 | counter = 'BRANCH' 122 | value = 'COVEREDRATIO' 123 | minimum = 0.8 124 | } 125 | limit { 126 | counter = 'METHOD' 127 | value = 'COVEREDRATIO' 128 | minimum = 0.8 129 | } 130 | limit { 131 | counter = 'CLASS' 132 | value = 'COVEREDRATIO' 133 | minimum = 0.8 134 | } 135 | } 136 | } 137 | dependsOn(jacocoTestReport) 138 | } 139 | 140 | license { 141 | header rootProject.file('config/copyright/copyright.header') 142 | excludes(["**/*.properties"]) 143 | strictCheck true 144 | } 145 | 146 | check.dependsOn jacocoTestCoverageVerification 147 | check.dependsOn licenseFormat 148 | //check.dependsOn license 149 | //check.dependsOn dependencyCheckAnalyze 150 | 151 | sonarqube { 152 | properties { 153 | property "sonar.projectName", "A JPA Query By Specification framework." 154 | property "sonar.projectKey", "wenhao_jpa-spec" 155 | } 156 | } 157 | 158 | def pomConfig = { 159 | licenses { 160 | license { 161 | name 'MIT License' 162 | url 'http://www.opensource.org/licenses/mit-license.php' 163 | distribution 'repo' 164 | } 165 | } 166 | 167 | scm { 168 | url 'https://github.com/wenhao/jpa-spec' 169 | connection 'https://github.com/wenhao/jpa-spec.git' 170 | developerConnection 'git@github.com:wenhao/jpa-spec.git' 171 | } 172 | 173 | developers { 174 | developer { 175 | id 'wenhao' 176 | name 'Hao Wen' 177 | email 'wenhao@126.com' 178 | organization 'Hao Wen' 179 | roles { 180 | role 'Developer' 181 | } 182 | } 183 | } 184 | 185 | } 186 | 187 | publishing { 188 | publications { 189 | maven(MavenPublication) { 190 | from components.java 191 | artifact sourcesJar 192 | artifact javadocJar 193 | groupId 'com.github.wenhao' 194 | artifactId 'jpa-spec' 195 | version '4.0.0-SNAPSHOT' 196 | pom.withXml { 197 | def root = asNode() 198 | root.appendNode('description', 'A JPA Query By Specification framework.') 199 | root.appendNode('name', 'jpa-spec') 200 | root.appendNode('url', 'https://github.com/wenhao/jpa-spec') 201 | root.appendNode('inceptionYear', '2016') 202 | root.children().last() + pomConfig 203 | } 204 | } 205 | } 206 | } 207 | 208 | // publish to jcenter 209 | bintray { 210 | user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') 211 | key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') 212 | configurations = ['archives'] 213 | publications = ['maven'] 214 | dryRun = false 215 | publish = true 216 | override = false 217 | pkg { 218 | repo = 'maven' 219 | name = 'jpa-spec' 220 | desc = 'A JPA Query By Specification framework' 221 | licenses = ['MIT'] 222 | websiteUrl = 'https://github.com/wenhao/jpa-spec' 223 | issueTrackerUrl = 'https://github.com/wenhao/jpa-spec/issues' 224 | vcsUrl = 'https://github.com/wenhao/jpa-spec' 225 | labels = ['jpa', 'spring-data'] 226 | publicDownloadNumbers = true 227 | githubRepo = 'wenhao/jpa-spec' 228 | githubReleaseNotesFile = 'README.md' 229 | version { 230 | name = '4.0.0-SNAPSHOT' 231 | desc = 'A JPA Query By Specification framework 4.0.0-SNAPSHOT' 232 | released = new Date() 233 | vcsTag = '4.0.0-SNAPSHOT' 234 | gpg { 235 | sign = true 236 | passphrase = 'passphrase' 237 | } 238 | mavenCentralSync { 239 | sync = true 240 | user = project.hasProperty('sonatypeUsername') 241 | password = project.hasProperty('sonatypePassword') 242 | close = '1' 243 | } 244 | } 245 | } 246 | } 247 | 248 | copy { 249 | from "./config/githooks/" 250 | into "./.git/hooks/" 251 | fileMode 0755 252 | } -------------------------------------------------------------------------------- /config/copyright/copyright.header: -------------------------------------------------------------------------------- 1 | Copyright © 2019, Wen Hao . 2 |

3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 |

10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 |

13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /config/githooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Regex & Usage 4 | commit_regex="^(\[\S+(\s\&\s\S+)*\]|merge)\s" 5 | error_message="Aborting commit. Commit message must start with [YOUR NAME]." 6 | usage="[YOUR NAME] Commit Message." 7 | 8 | if ! grep -qE "$commit_regex" "$1"; then 9 | printf "error_message\n" 10 | printf "Commit Regex: ${commit_regex}\n" 11 | printf "Usage: ${usage}\n" 12 | exit 1 13 | fi 14 | -------------------------------------------------------------------------------- /config/githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | command="./gradlew build" 4 | 5 | ${command} 6 | 7 | if ! [ $? -eq 0 ]; then 8 | printf "Make sure successfully execute command: $command." 9 | exit 1 10 | fi 11 | -------------------------------------------------------------------------------- /docs/3.0.0.md: -------------------------------------------------------------------------------- 1 | ### Gradle 2 | 3 | ```groovy 4 | repositories { 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | compile 'com.github.wenhao:jpa-spec:3.0.0' 10 | } 11 | ``` 12 | 13 | ### Maven 14 | 15 | ```xml 16 | 17 | com.github.wenhao 18 | jpa-spec 19 | 3.0.0 20 | 21 | ``` 22 | 23 | ### Specification By Examples: 24 | 25 | #### Each specification support three parameters: 26 | 27 | 1. **condition**: if true(default), apply this specification. 28 | 2. **property**: field name. 29 | 3. **values**: compare value with model, eq/ne/like support multiple values. 30 | 31 | #### General Example 32 | 33 | each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. 34 | 35 | ```java 36 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 37 | } 38 | ``` 39 | 40 | ```java 41 | public Page findAll(SearchRequest request) { 42 | Specification specification = Specifications.builder() 43 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 44 | .gt(Objects.nonNull(request.getAge()), "age", 18) 45 | .between("birthday", new Range<>(new Date(), new Date())) 46 | .like("nickName", "%og%", "%me") 47 | .build(); 48 | 49 | return personRepository.findAll(specification, new PageRequest(0, 15)); 50 | } 51 | ``` 52 | 53 | #### Equal/NotEqual Example 54 | 55 | find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. 56 | 57 | **Test:** [EqualTest.java] and [NotEqualTest.java] 58 | 59 | ```java 60 | public List findAll(SearchRequest request) { 61 | Specification specification = Specifications.builder() 62 | .eq("nickName", "dog") 63 | .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) 64 | .eq("company", null) //or eq("company", (Object) null) 65 | .build(); 66 | 67 | return personRepository.findAll(specification); 68 | } 69 | ``` 70 | 71 | #### In/NotIn Example 72 | 73 | find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". 74 | 75 | **Test:** [InTest.java] 76 | 77 | ```java 78 | public List findAll(SearchRequest request) { 79 | Specification specification = Specifications.builder() 80 | .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") 81 | .notIn("company", "ThoughtWorks", "IBM") 82 | .build(); 83 | 84 | return personRepository.findAll(specification); 85 | } 86 | ``` 87 | 88 | #### Numerical Example 89 | 90 | find any people age bigger than 18. 91 | 92 | **Test:** [GtTest.java] 93 | 94 | ```java 95 | public List findAll(SearchRequest request) { 96 | Specification specification = Specifications.builder() 97 | .gt(Objects.nonNull(request.getAge()), "age", 18) 98 | .build(); 99 | 100 | return personRepository.findAll(specification); 101 | } 102 | ``` 103 | 104 | #### Between Example 105 | 106 | find any person age between 18 and 25, birthday between someday and someday. 107 | 108 | **Test:** [BetweenTest.java] 109 | 110 | ```java 111 | public List findAll(SearchRequest request) { 112 | Specification specification = Specifications.builder() 113 | .between(Objects.nonNull(request.getAge(), "age", new Range<>(18, 25)) 114 | .between("birthday", new Range<>(new Date(), new Date())) 115 | .build(); 116 | 117 | return personRepository.findAll(specification); 118 | } 119 | ``` 120 | 121 | #### Like/NotLike Example 122 | 123 | find any person name like %ac% or %og%, company not like %ec%. 124 | 125 | **Test:** [LikeTest.java] and [NotLikeTest.java] 126 | 127 | ```java 128 | public Page findAll(SearchRequest request) { 129 | Specification specification = Specifications.builder() 130 | .like("name", "ac", "%og%") 131 | .notLike("company", "ec") 132 | .build(); 133 | 134 | return personRepository.findAll(specification); 135 | } 136 | ``` 137 | 138 | #### Or 139 | 140 | support or specifications. 141 | 142 | **Test:** [OrTest.java] 143 | 144 | ```java 145 | public List findAll(SearchRequest request) { 146 | Specification specification = Specifications.builder() 147 | .and(OrSpecifications.builder() 148 | .like("name", "%ac%") 149 | .gt("age", 19) 150 | .build()) 151 | .build(); 152 | 153 | return phoneRepository.findAll(specification); 154 | } 155 | ``` 156 | 157 | #### Join 158 | 159 | each specification support association query as left join. 160 | 161 | **Test:** [JoinTest.java] 162 | 163 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 164 | 165 | ```java 166 | public List findAll(SearchRequest request) { 167 | Specification specification = Specifications.builder() 168 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 169 | .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") 170 | .build(); 171 | 172 | return phoneRepository.findAll(specification); 173 | } 174 | ``` 175 | 176 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 177 | 178 | ```java 179 | public List findAll(SearchRequest request) { 180 | Specification specification = Specifications.builder() 181 | .between("age", new Range<>(10, 35)) 182 | .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") 183 | .build(); 184 | 185 | return phoneRepository.findAll(specification); 186 | } 187 | ``` 188 | 189 | #### Custom Specification 190 | 191 | You can custom specification to do the @ManyToOne and @ManyToMany as well. 192 | 193 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 194 | 195 | **Test:** [AndTest.java] 196 | 197 | ```java 198 | public List findAll(SearchRequest request) { 199 | Specification specification = Specifications.builder() 200 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 201 | .and(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { 202 | Path person = root.get("person"); 203 | return cb.equal(person.get("name"), "Jack"); 204 | }) 205 | .build(); 206 | 207 | return phoneRepository.findAll(specification); 208 | } 209 | ``` 210 | 211 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 212 | 213 | **Test:** [AndTest.java] 214 | 215 | ```java 216 | public List findAll(SearchRequest request) { 217 | Specification specification = Specifications.builder() 218 | .between("age", new Range<>(10, 35)) 219 | .and(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { 220 | Join address = root.join("addresses", JoinType.LEFT); 221 | return cb.equal(address.get("street"), "Chengdu"); 222 | })) 223 | .build(); 224 | 225 | return phoneRepository.findAll(specification); 226 | } 227 | ``` 228 | 229 | #### Sort 230 | 231 | **Test:** [SortTest.java] 232 | 233 | ```java 234 | public List findAll(SearchRequest request) { 235 | Specification specification = Specifications.builder() 236 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 237 | .gt("age", 18) 238 | .between("birthday", new Range<>(new Date(), new Date())) 239 | .like("nickName", "%og%") 240 | .build(); 241 | 242 | Sort sort = Sorts.builder() 243 | .desc(StringUtils.isNotBlank(request.getName()), "name") 244 | .asc("birthday") 245 | .build(); 246 | 247 | return personRepository.findAll(specification, sort); 248 | } 249 | ``` 250 | 251 | #### Pagination 252 | 253 | find person by pagination and sort by name desc and birthday asc. 254 | 255 | ```java 256 | public Page findAll(SearchRequest request) { 257 | Specification specification = Specifications.builder() 258 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 259 | .gt("age", 18) 260 | .between("birthday", new Range<>(new Date(), new Date())) 261 | .like("nickName", "%og%") 262 | .build(); 263 | 264 | Sort sort = Sorts.builder() 265 | .desc(StringUtils.isNotBlank(request.getName()), "name") 266 | .asc("birthday") 267 | .build(); 268 | 269 | return personRepository.findAll(specification, new PageRequest(0, 15, sort)); 270 | } 271 | ``` 272 | 273 | #### Virtual View 274 | 275 | Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. 276 | 277 | There is no difference between a view and a database table for a Hibernate mapping. 278 | 279 | **Test:** [VirtualViewTest.java] 280 | 281 | ```java 282 | @Entity 283 | @Immutable 284 | @Subselect("SELECT p.id, p.name, p.age, ic.number " + 285 | "FROM person p " + 286 | "LEFT JOIN id_card ic " + 287 | "ON p.id_card_id=ic.id") 288 | public class PersonIdCard { 289 | @Id 290 | private Long id; 291 | private String name; 292 | private Integer age; 293 | private String number; 294 | 295 | // Getters and setters are omitted for brevity 296 | } 297 | ``` 298 | 299 | ```java 300 | public List findAll(SearchRequest request) { 301 | Specification specification = Specifications.builder() 302 | .gt(Objects.nonNull(request.getAge()), "age", 18) 303 | .build(); 304 | 305 | return personIdCardRepository.findAll(specification); 306 | } 307 | ``` 308 | 309 | #### Projection, GroupBy, Aggregation 310 | 311 | Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, 312 | 313 | furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. 314 | 315 | Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. 316 | 317 | ### Copyright and license 318 | 319 | Copyright © 2016-2017 Wen Hao 320 | 321 | Licensed under [Apache License] 322 | 323 | [EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java 324 | [NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java 325 | [InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java 326 | [GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java 327 | [BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java 328 | [LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java 329 | [NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java 330 | [OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java 331 | [AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java 332 | [JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java 333 | [SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java 334 | [VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java 335 | [MIT License]: ./LICENSE -------------------------------------------------------------------------------- /docs/3.1.0.md: -------------------------------------------------------------------------------- 1 | ### Gradle 2 | 3 | ```groovy 4 | repositories { 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | compile 'com.github.wenhao:jpa-spec:3.1.0' 10 | } 11 | ``` 12 | 13 | ### Maven 14 | 15 | ```xml 16 | 17 | com.github.wenhao 18 | jpa-spec 19 | 3.1.0 20 | 21 | ``` 22 | 23 | ### Specification By Examples: 24 | 25 | #### Each specification support three parameters: 26 | 27 | 1. **condition**: if true(default), apply this specification. 28 | 2. **property**: field name. 29 | 3. **values**: compare value with model, eq/ne/like support multiple values. 30 | 31 | #### General Example 32 | 33 | each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. 34 | 35 | ```java 36 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 37 | } 38 | ``` 39 | 40 | ```java 41 | public Page findAll(SearchRequest request) { 42 | Specification specification = Specifications.and() 43 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 44 | .gt(Objects.nonNull(request.getAge()), "age", 18) 45 | .between("birthday", new Range<>(new Date(), new Date())) 46 | .like("nickName", "%og%", "%me") 47 | .build(); 48 | 49 | return personRepository.findAll(specification, new PageRequest(0, 15)); 50 | } 51 | ``` 52 | 53 | #### Equal/NotEqual Example 54 | 55 | find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. 56 | 57 | **Test:** [EqualTest.java] and [NotEqualTest.java] 58 | 59 | ```java 60 | public List findAll(SearchRequest request) { 61 | Specification specification = Specifications.and() 62 | .eq("nickName", "dog") 63 | .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) 64 | .eq("company", null) //or eq("company", (Object) null) 65 | .build(); 66 | 67 | return personRepository.findAll(specification); 68 | } 69 | ``` 70 | 71 | #### In/NotIn Example 72 | 73 | find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". 74 | 75 | **Test:** [InTest.java] 76 | 77 | ```java 78 | public List findAll(SearchRequest request) { 79 | Specification specification = Specifications.and() 80 | .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") 81 | .notIn("company", "ThoughtWorks", "IBM") 82 | .build(); 83 | 84 | return personRepository.findAll(specification); 85 | } 86 | ``` 87 | 88 | #### Numerical Example 89 | 90 | find any people age bigger than 18. 91 | 92 | **Test:** [GtTest.java] 93 | 94 | ```java 95 | public List findAll(SearchRequest request) { 96 | Specification specification = Specifications.and() 97 | .gt(Objects.nonNull(request.getAge()), "age", 18) 98 | .build(); 99 | 100 | return personRepository.findAll(specification); 101 | } 102 | ``` 103 | 104 | #### Between Example 105 | 106 | find any person age between 18 and 25, birthday between someday and someday. 107 | 108 | **Test:** [BetweenTest.java] 109 | 110 | ```java 111 | public List findAll(SearchRequest request) { 112 | Specification specification = Specifications.and() 113 | .between(Objects.nonNull(request.getAge(), "age", new Range<>(18, 25)) 114 | .between("birthday", new Range<>(new Date(), new Date())) 115 | .build(); 116 | 117 | return personRepository.findAll(specification); 118 | } 119 | ``` 120 | 121 | #### Like/NotLike Example 122 | 123 | find any person name like %ac% or %og%, company not like %ec%. 124 | 125 | **Test:** [LikeTest.java] and [NotLikeTest.java] 126 | 127 | ```java 128 | public Page findAll(SearchRequest request) { 129 | Specification specification = Specifications.and() 130 | .like("name", "ac", "%og%") 131 | .notLike("company", "ec") 132 | .build(); 133 | 134 | return personRepository.findAll(specification); 135 | } 136 | ``` 137 | 138 | #### Or 139 | 140 | support or specifications. 141 | 142 | **Test:** [OrTest.java] 143 | 144 | ```java 145 | public List findAll(SearchRequest request) { 146 | Specification specification = Specifications.or() 147 | .like("name", "%ac%") 148 | .gt("age", 19) 149 | .build(); 150 | 151 | return phoneRepository.findAll(specification); 152 | } 153 | ``` 154 | 155 | #### Join 156 | 157 | each specification support association query as left join. 158 | 159 | **Test:** [JoinTest.java] 160 | 161 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 162 | 163 | ```java 164 | public List findAll(SearchRequest request) { 165 | Specification specification = Specifications.and() 166 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 167 | .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") 168 | .build(); 169 | 170 | return phoneRepository.findAll(specification); 171 | } 172 | ``` 173 | 174 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 175 | 176 | ```java 177 | public List findAll(SearchRequest request) { 178 | Specification specification = Specifications.and() 179 | .between("age", new Range<>(10, 35)) 180 | .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") 181 | .build(); 182 | 183 | return phoneRepository.findAll(specification); 184 | } 185 | ``` 186 | 187 | #### Custom Specification 188 | 189 | You can custom specification to do the @ManyToOne and @ManyToMany as well. 190 | 191 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 192 | 193 | **Test:** [AndTest.java] 194 | 195 | ```java 196 | public List findAll(SearchRequest request) { 197 | Specification specification = Specifications.and() 198 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 199 | .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { 200 | Path person = root.get("person"); 201 | return cb.equal(person.get("name"), "Jack"); 202 | }) 203 | .build(); 204 | 205 | return phoneRepository.findAll(specification); 206 | } 207 | ``` 208 | 209 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 210 | 211 | **Test:** [AndTest.java] 212 | 213 | ```java 214 | public List findAll(SearchRequest request) { 215 | Specification specification = Specifications.and() 216 | .between("age", new Range<>(10, 35)) 217 | .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { 218 | Join address = root.join("addresses", JoinType.LEFT); 219 | return cb.equal(address.get("street"), "Chengdu"); 220 | })) 221 | .build(); 222 | 223 | return phoneRepository.findAll(specification); 224 | } 225 | ``` 226 | 227 | #### Sort 228 | 229 | **Test:** [SortTest.java] 230 | 231 | ```java 232 | public List findAll(SearchRequest request) { 233 | Specification specification = Specifications.and() 234 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 235 | .gt("age", 18) 236 | .between("birthday", new Range<>(new Date(), new Date())) 237 | .like("nickName", "%og%") 238 | .build(); 239 | 240 | Sort sort = Sorts.builder() 241 | .desc(StringUtils.isNotBlank(request.getName()), "name") 242 | .asc("birthday") 243 | .build(); 244 | 245 | return personRepository.findAll(specification, sort); 246 | } 247 | ``` 248 | 249 | #### Pagination 250 | 251 | find person by pagination and sort by name desc and birthday asc. 252 | 253 | ```java 254 | public Page findAll(SearchRequest request) { 255 | Specification specification = Specifications.and() 256 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 257 | .gt("age", 18) 258 | .between("birthday", new Range<>(new Date(), new Date())) 259 | .like("nickName", "%og%") 260 | .build(); 261 | 262 | Sort sort = Sorts.builder() 263 | .desc(StringUtils.isNotBlank(request.getName()), "name") 264 | .asc("birthday") 265 | .build(); 266 | 267 | return personRepository.findAll(specification, new PageRequest(0, 15, sort)); 268 | } 269 | ``` 270 | 271 | #### Virtual View 272 | 273 | Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. 274 | 275 | There is no difference between a view and a database table for a Hibernate mapping. 276 | 277 | **Test:** [VirtualViewTest.java] 278 | 279 | ```java 280 | @Entity 281 | @Immutable 282 | @Subselect("SELECT p.id, p.name, p.age, ic.number " + 283 | "FROM person p " + 284 | "LEFT JOIN id_card ic " + 285 | "ON p.id_card_id=ic.id") 286 | public class PersonIdCard { 287 | @Id 288 | private Long id; 289 | private String name; 290 | private Integer age; 291 | private String number; 292 | 293 | // Getters and setters are omitted for brevity 294 | } 295 | ``` 296 | 297 | ```java 298 | public List findAll(SearchRequest request) { 299 | Specification specification = Specifications.and() 300 | .gt(Objects.nonNull(request.getAge()), "age", 18) 301 | .build(); 302 | 303 | return personIdCardRepository.findAll(specification); 304 | } 305 | ``` 306 | 307 | #### Projection, GroupBy, Aggregation 308 | 309 | Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, 310 | 311 | furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. 312 | 313 | Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. 314 | 315 | ### Copyright and license 316 | 317 | Copyright © 2016-2017 Wen Hao 318 | 319 | Licensed under [Apache License] 320 | 321 | [EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java 322 | [NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java 323 | [InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java 324 | [GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java 325 | [BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java 326 | [LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java 327 | [NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java 328 | [OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java 329 | [AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java 330 | [JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java 331 | [SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java 332 | [VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java 333 | [MIT License]: ./LICENSE 334 | -------------------------------------------------------------------------------- /docs/3.1.1.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | 1. Upgrade to Java 1.8. 4 | 2. Comparison support all class which implement Comparable interface. 5 | 6 | ### Gradle 7 | 8 | ```groovy 9 | repositories { 10 | jcenter() 11 | } 12 | 13 | dependencies { 14 | compile 'com.github.wenhao:jpa-spec:3.1.1' 15 | } 16 | ``` 17 | 18 | ### Maven 19 | 20 | ```xml 21 | 22 | com.github.wenhao 23 | jpa-spec 24 | 3.1.1 25 | 26 | ``` 27 | 28 | ### Specification By Examples: 29 | 30 | #### Each specification support three parameters: 31 | 32 | 1. **condition**: if true(default), apply this specification. 33 | 2. **property**: field name. 34 | 3. **values**: compare value with model, eq/ne/like support multiple values. 35 | 36 | #### General Example 37 | 38 | each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. 39 | 40 | ```java 41 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 42 | } 43 | ``` 44 | 45 | ```java 46 | public Page findAll(SearchRequest request) { 47 | Specification specification = Specifications.and() 48 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 49 | .gt(Objects.nonNull(request.getAge()), "age", 18) 50 | .between("birthday", new Range<>(new Date(), new Date())) 51 | .like("nickName", "%og%", "%me") 52 | .build(); 53 | 54 | return personRepository.findAll(specification, new PageRequest(0, 15)); 55 | } 56 | ``` 57 | 58 | #### Equal/NotEqual Example 59 | 60 | find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. 61 | 62 | **Test:** [EqualTest.java] and [NotEqualTest.java] 63 | 64 | ```java 65 | public List findAll(SearchRequest request) { 66 | Specification specification = Specifications.and() 67 | .eq("nickName", "dog") 68 | .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) 69 | .eq("company", null) //or eq("company", (Object) null) 70 | .build(); 71 | 72 | return personRepository.findAll(specification); 73 | } 74 | ``` 75 | 76 | #### In/NotIn Example 77 | 78 | find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". 79 | 80 | **Test:** [InTest.java] 81 | 82 | ```java 83 | public List findAll(SearchRequest request) { 84 | Specification specification = Specifications.and() 85 | .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") 86 | .notIn("company", "ThoughtWorks", "IBM") 87 | .build(); 88 | 89 | return personRepository.findAll(specification); 90 | } 91 | ``` 92 | 93 | #### Comparison Example 94 | 95 | Support any comparison class which implements Comparable interface, find any people age bigger than 18. 96 | 97 | **Test:** [GtTest.java] 98 | 99 | ```java 100 | public List findAll(SearchRequest request) { 101 | Specification specification = Specifications.and() 102 | .gt(Objects.nonNull(request.getAge()), "age", 18) 103 | .lt("birthday", new Date()) 104 | .build(); 105 | 106 | return personRepository.findAll(specification); 107 | } 108 | ``` 109 | 110 | #### Between Example 111 | 112 | find any person age between 18 and 25, birthday between someday and someday. 113 | 114 | **Test:** [BetweenTest.java] 115 | 116 | ```java 117 | public List findAll(SearchRequest request) { 118 | Specification specification = Specifications.and() 119 | .between(Objects.nonNull(request.getAge(), "age", new Range<>(18, 25)) 120 | .between("birthday", new Range<>(new Date(), new Date())) 121 | .build(); 122 | 123 | return personRepository.findAll(specification); 124 | } 125 | ``` 126 | 127 | #### Like/NotLike Example 128 | 129 | find any person name like %ac% or %og%, company not like %ec%. 130 | 131 | **Test:** [LikeTest.java] and [NotLikeTest.java] 132 | 133 | ```java 134 | public Page findAll(SearchRequest request) { 135 | Specification specification = Specifications.and() 136 | .like("name", "ac", "%og%") 137 | .notLike("company", "ec") 138 | .build(); 139 | 140 | return personRepository.findAll(specification); 141 | } 142 | ``` 143 | 144 | #### Or 145 | 146 | support or specifications. 147 | 148 | **Test:** [OrTest.java] 149 | 150 | ```java 151 | public List findAll(SearchRequest request) { 152 | Specification specification = Specifications.or() 153 | .like("name", "%ac%") 154 | .gt("age", 19) 155 | .build(); 156 | 157 | return phoneRepository.findAll(specification); 158 | } 159 | ``` 160 | 161 | #### Join 162 | 163 | each specification support association query as left join. 164 | 165 | **Test:** [JoinTest.java] 166 | 167 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 168 | 169 | ```java 170 | public List findAll(SearchRequest request) { 171 | Specification specification = Specifications.and() 172 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 173 | .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") 174 | .build(); 175 | 176 | return phoneRepository.findAll(specification); 177 | } 178 | ``` 179 | 180 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 181 | 182 | ```java 183 | public List findAll(SearchRequest request) { 184 | Specification specification = Specifications.and() 185 | .between("age", new Range<>(10, 35)) 186 | .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") 187 | .build(); 188 | 189 | return phoneRepository.findAll(specification); 190 | } 191 | ``` 192 | 193 | #### Custom Specification 194 | 195 | You can custom specification to do the @ManyToOne and @ManyToMany as well. 196 | 197 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 198 | 199 | **Test:** [AndTest.java] 200 | 201 | ```java 202 | public List findAll(SearchRequest request) { 203 | Specification specification = Specifications.and() 204 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 205 | .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { 206 | Path person = root.get("person"); 207 | return cb.equal(person.get("name"), "Jack"); 208 | }) 209 | .build(); 210 | 211 | return phoneRepository.findAll(specification); 212 | } 213 | ``` 214 | 215 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 216 | 217 | **Test:** [AndTest.java] 218 | 219 | ```java 220 | public List findAll(SearchRequest request) { 221 | Specification specification = Specifications.and() 222 | .between("age", new Range<>(10, 35)) 223 | .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { 224 | Join address = root.join("addresses", JoinType.LEFT); 225 | return cb.equal(address.get("street"), "Chengdu"); 226 | })) 227 | .build(); 228 | 229 | return phoneRepository.findAll(specification); 230 | } 231 | ``` 232 | 233 | #### Sort 234 | 235 | **Test:** [SortTest.java] 236 | 237 | ```java 238 | public List findAll(SearchRequest request) { 239 | Specification specification = Specifications.and() 240 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 241 | .gt("age", 18) 242 | .between("birthday", new Range<>(new Date(), new Date())) 243 | .like("nickName", "%og%") 244 | .build(); 245 | 246 | Sort sort = Sorts.builder() 247 | .desc(StringUtils.isNotBlank(request.getName()), "name") 248 | .asc("birthday") 249 | .build(); 250 | 251 | return personRepository.findAll(specification, sort); 252 | } 253 | ``` 254 | 255 | #### Pagination 256 | 257 | find person by pagination and sort by name desc and birthday asc. 258 | 259 | ```java 260 | public Page findAll(SearchRequest request) { 261 | Specification specification = Specifications.and() 262 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 263 | .gt("age", 18) 264 | .between("birthday", new Range<>(new Date(), new Date())) 265 | .like("nickName", "%og%") 266 | .build(); 267 | 268 | Sort sort = Sorts.builder() 269 | .desc(StringUtils.isNotBlank(request.getName()), "name") 270 | .asc("birthday") 271 | .build(); 272 | 273 | return personRepository.findAll(specification, new PageRequest(0, 15, sort)); 274 | } 275 | ``` 276 | 277 | #### Virtual View 278 | 279 | Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. 280 | 281 | There is no difference between a view and a database table for a Hibernate mapping. 282 | 283 | **Test:** [VirtualViewTest.java] 284 | 285 | ```java 286 | @Entity 287 | @Immutable 288 | @Subselect("SELECT p.id, p.name, p.age, ic.number " + 289 | "FROM person p " + 290 | "LEFT JOIN id_card ic " + 291 | "ON p.id_card_id=ic.id") 292 | public class PersonIdCard { 293 | @Id 294 | private Long id; 295 | private String name; 296 | private Integer age; 297 | private String number; 298 | 299 | // Getters and setters are omitted for brevity 300 | } 301 | ``` 302 | 303 | ```java 304 | public List findAll(SearchRequest request) { 305 | Specification specification = Specifications.and() 306 | .gt(Objects.nonNull(request.getAge()), "age", 18) 307 | .build(); 308 | 309 | return personIdCardRepository.findAll(specification); 310 | } 311 | ``` 312 | 313 | #### Projection, GroupBy, Aggregation 314 | 315 | Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, 316 | 317 | furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. 318 | 319 | Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. 320 | 321 | ### Copyright and license 322 | 323 | Copyright © 2016-2017 Wen Hao 324 | 325 | Licensed under [Apache License] 326 | 327 | [EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java 328 | [NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java 329 | [InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java 330 | [GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java 331 | [BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java 332 | [LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java 333 | [NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java 334 | [OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java 335 | [AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java 336 | [JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java 337 | [SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java 338 | [VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java 339 | [MIT License]: ./LICENSE 340 | -------------------------------------------------------------------------------- /docs/3.2.1.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | 1. Upgrade spring-data-jpa dependency to 2.0.5.RELEASE. 4 | 5 | ### Gradle 6 | 7 | ```groovy 8 | repositories { 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | compile 'com.github.wenhao:jpa-spec:3.2.1' 14 | } 15 | ``` 16 | 17 | ### Maven 18 | 19 | ```xml 20 | 21 | com.github.wenhao 22 | jpa-spec 23 | 3.2.1 24 | 25 | ``` 26 | 27 | ### Specification By Examples: 28 | 29 | #### Each specification support three parameters: 30 | 31 | 1. **condition**: if true(default), apply this specification. 32 | 2. **property**: field name. 33 | 3. **values**: compare value with model, eq/ne/like support multiple values. 34 | 35 | #### General Example 36 | 37 | each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. 38 | 39 | ```java 40 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 41 | } 42 | ``` 43 | 44 | ```java 45 | public Page findAll(SearchRequest request) { 46 | Specification specification = Specifications.and() 47 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 48 | .gt(Objects.nonNull(request.getAge()), "age", 18) 49 | .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) 50 | .like("nickName", "%og%", "%me") 51 | .build(); 52 | 53 | return personRepository.findAll(specification, new PageRequest(0, 15)); 54 | } 55 | ``` 56 | 57 | #### Equal/NotEqual Example 58 | 59 | find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. 60 | 61 | **Test:** [EqualTest.java] and [NotEqualTest.java] 62 | 63 | ```java 64 | public List findAll(SearchRequest request) { 65 | Specification specification = Specifications.and() 66 | .eq("nickName", "dog") 67 | .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) 68 | .eq("company", null) //or eq("company", (Object) null) 69 | .build(); 70 | 71 | return personRepository.findAll(specification); 72 | } 73 | ``` 74 | 75 | #### In/NotIn Example 76 | 77 | find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". 78 | 79 | **Test:** [InTest.java] 80 | 81 | ```java 82 | public List findAll(SearchRequest request) { 83 | Specification specification = Specifications.and() 84 | .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") 85 | .notIn("company", "ThoughtWorks", "IBM") 86 | .build(); 87 | 88 | return personRepository.findAll(specification); 89 | } 90 | ``` 91 | 92 | #### Comparison Example 93 | 94 | Support any comparison class which implements Comparable interface, find any people age bigger than 18. 95 | 96 | **Test:** [GtTest.java] 97 | 98 | ```java 99 | public List findAll(SearchRequest request) { 100 | Specification specification = Specifications.and() 101 | .gt(Objects.nonNull(request.getAge()), "age", 18) 102 | .lt("birthday", new Date()) 103 | .build(); 104 | 105 | return personRepository.findAll(specification); 106 | } 107 | ``` 108 | 109 | #### Between Example 110 | 111 | find any person age between 18 and 25, birthday between someday and someday. 112 | 113 | **Test:** [BetweenTest.java] 114 | 115 | ```java 116 | public List findAll(SearchRequest request) { 117 | Specification specification = Specifications.and() 118 | .between(Objects.nonNull(request.getAge(), "age", Range.of(inclusive(18), inclusive(25))) 119 | .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) 120 | .build(); 121 | 122 | return personRepository.findAll(specification); 123 | } 124 | ``` 125 | 126 | #### Like/NotLike Example 127 | 128 | find any person name like %ac% or %og%, company not like %ec%. 129 | 130 | **Test:** [LikeTest.java] and [NotLikeTest.java] 131 | 132 | ```java 133 | public Page findAll(SearchRequest request) { 134 | Specification specification = Specifications.and() 135 | .like("name", "ac", "%og%") 136 | .notLike("company", "ec") 137 | .build(); 138 | 139 | return personRepository.findAll(specification); 140 | } 141 | ``` 142 | 143 | #### Or 144 | 145 | support or specifications. 146 | 147 | **Test:** [OrTest.java] 148 | 149 | ```java 150 | public List findAll(SearchRequest request) { 151 | Specification specification = Specifications.or() 152 | .like("name", "%ac%") 153 | .gt("age", 19) 154 | .build(); 155 | 156 | return phoneRepository.findAll(specification); 157 | } 158 | ``` 159 | 160 | #### Join 161 | 162 | each specification support association query as left join. 163 | 164 | **Test:** [JoinTest.java] 165 | 166 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 167 | 168 | ```java 169 | public List findAll(SearchRequest request) { 170 | Specification specification = Specifications.and() 171 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 172 | .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") 173 | .build(); 174 | 175 | return phoneRepository.findAll(specification); 176 | } 177 | ``` 178 | 179 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 180 | 181 | ```java 182 | public List findAll(SearchRequest request) { 183 | Specification specification = Specifications.and() 184 | .between("age", Range.of(inclusive(10), inclusive(35))) 185 | .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") 186 | .build(); 187 | 188 | return phoneRepository.findAll(specification); 189 | } 190 | ``` 191 | 192 | #### Custom Specification 193 | 194 | You can custom specification to do the @ManyToOne and @ManyToMany as well. 195 | 196 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 197 | 198 | **Test:** [AndTest.java] 199 | 200 | ```java 201 | public List findAll(SearchRequest request) { 202 | Specification specification = Specifications.and() 203 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 204 | .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { 205 | Path person = root.get("person"); 206 | return cb.equal(person.get("name"), "Jack"); 207 | }) 208 | .build(); 209 | 210 | return phoneRepository.findAll(specification); 211 | } 212 | ``` 213 | 214 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 215 | 216 | **Test:** [AndTest.java] 217 | 218 | ```java 219 | public List findAll(SearchRequest request) { 220 | Specification specification = Specifications.and() 221 | .between("age", Range.of(inclusive(10), inclusive(35))) 222 | .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { 223 | Join address = root.join("addresses", JoinType.LEFT); 224 | return cb.equal(address.get("street"), "Chengdu"); 225 | })) 226 | .build(); 227 | 228 | return phoneRepository.findAll(specification); 229 | } 230 | ``` 231 | 232 | #### Sort 233 | 234 | **Test:** [SortTest.java] 235 | 236 | ```java 237 | public List findAll(SearchRequest request) { 238 | Specification specification = Specifications.and() 239 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 240 | .gt("age", 18) 241 | .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) 242 | .like("nickName", "%og%") 243 | .build(); 244 | 245 | Sort sort = Sorts.builder() 246 | .desc(StringUtils.isNotBlank(request.getName()), "name") 247 | .asc("birthday") 248 | .build(); 249 | 250 | return personRepository.findAll(specification, sort); 251 | } 252 | ``` 253 | 254 | #### Pagination 255 | 256 | find person by pagination and sort by name desc and birthday asc. 257 | 258 | ```java 259 | public Page findAll(SearchRequest request) { 260 | Specification specification = Specifications.and() 261 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 262 | .gt("age", 18) 263 | .between("birthday", Range.of(inclusive(new Date()), inclusive(new Date()))) 264 | .like("nickName", "%og%") 265 | .build(); 266 | 267 | Sort sort = Sorts.builder() 268 | .desc(StringUtils.isNotBlank(request.getName()), "name") 269 | .asc("birthday") 270 | .build(); 271 | 272 | return personRepository.findAll(specification, new PageRequest(0, 15, sort)); 273 | } 274 | ``` 275 | 276 | #### Virtual View 277 | 278 | Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. 279 | 280 | There is no difference between a view and a database table for a Hibernate mapping. 281 | 282 | **Test:** [VirtualViewTest.java] 283 | 284 | ```java 285 | @Entity 286 | @Immutable 287 | @Subselect("SELECT p.id, p.name, p.age, ic.number " + 288 | "FROM person p " + 289 | "LEFT JOIN id_card ic " + 290 | "ON p.id_card_id=ic.id") 291 | public class PersonIdCard { 292 | @Id 293 | private Long id; 294 | private String name; 295 | private Integer age; 296 | private String number; 297 | 298 | // Getters and setters are omitted for brevity 299 | } 300 | ``` 301 | 302 | ```java 303 | public List findAll(SearchRequest request) { 304 | Specification specification = Specifications.and() 305 | .gt(Objects.nonNull(request.getAge()), "age", 18) 306 | .build(); 307 | 308 | return personIdCardRepository.findAll(specification); 309 | } 310 | ``` 311 | 312 | #### Projection, GroupBy, Aggregation 313 | 314 | Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, 315 | 316 | furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. 317 | 318 | Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. 319 | 320 | ### Copyright and license 321 | 322 | Copyright © 2016-2018 Wen Hao 323 | 324 | Licensed under [Apache License] 325 | 326 | [EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java 327 | [NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java 328 | [InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java 329 | [GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java 330 | [BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java 331 | [LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java 332 | [NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java 333 | [OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java 334 | [AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java 335 | [JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java 336 | [SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java 337 | [VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java 338 | [MIT License]: ./LICENSE 339 | -------------------------------------------------------------------------------- /docs/3.2.4.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | 1. Fix bug, Spring Data JPA Range class require value must not be null, using guava Range instead. 4 | 5 | ### Gradle 6 | 7 | ```groovy 8 | repositories { 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | compile 'com.github.wenhao:jpa-spec:3.2.3' 14 | } 15 | ``` 16 | 17 | ### Maven 18 | 19 | ```xml 20 | 21 | com.github.wenhao 22 | jpa-spec 23 | 3.2.3 24 | 25 | ``` 26 | 27 | 28 | ### Maven exclude dependencies 29 | 30 | ```xml 31 | 32 | com.github.wenhao 33 | jpa-spec 34 | 3.2.3 35 | 36 | 37 | org.hibernate.javax.persistence 38 | hibernate-jpa-2.1-api 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-jpa 43 | 44 | 45 | org.springframework.data 46 | spring-data-jpa 47 | 48 | 49 | 50 | ``` 51 | 52 | ### Specification By Examples: 53 | 54 | #### Each specification support three parameters: 55 | 56 | 1. **condition**: if true(default), apply this specification. 57 | 2. **property**: field name. 58 | 3. **values**: compare value with model, eq/ne/like support multiple values. 59 | 60 | #### General Example 61 | 62 | each Repository class should extends from two super class **JpaRepository** and **JpaSpecificationExecutor**. 63 | 64 | ```java 65 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 66 | } 67 | ``` 68 | 69 | ```java 70 | public Page findAll(SearchRequest request) { 71 | Specification specification = Specifications.and() 72 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 73 | .gt(Objects.nonNull(request.getAge()), "age", 18) 74 | .between("birthday", new Date(), new Date()) 75 | .like("nickName", "%og%", "%me") 76 | .build(); 77 | 78 | return personRepository.findAll(specification, new PageRequest(0, 15)); 79 | } 80 | ``` 81 | 82 | #### Equal/NotEqual Example 83 | 84 | find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null. 85 | 86 | **Test:** [EqualTest.java] and [NotEqualTest.java] 87 | 88 | ```java 89 | public List findAll(SearchRequest request) { 90 | Specification specification = Specifications.and() 91 | .eq("nickName", "dog") 92 | .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null) 93 | .eq("company", null) //or eq("company", (Object) null) 94 | .build(); 95 | 96 | return personRepository.findAll(specification); 97 | } 98 | ``` 99 | 100 | #### In/NotIn Example 101 | 102 | find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM". 103 | 104 | **Test:** [InTest.java] 105 | 106 | ```java 107 | public List findAll(SearchRequest request) { 108 | Specification specification = Specifications.and() 109 | .in("name", request.getNames().toArray()) //or in("name", "Jack", "Eric") 110 | .notIn("company", "ThoughtWorks", "IBM") 111 | .build(); 112 | 113 | return personRepository.findAll(specification); 114 | } 115 | ``` 116 | 117 | #### Comparison Example 118 | 119 | Support any comparison class which implements Comparable interface, find any people age bigger than 18. 120 | 121 | **Test:** [GtTest.java] 122 | 123 | ```java 124 | public List findAll(SearchRequest request) { 125 | Specification specification = Specifications.and() 126 | .gt(Objects.nonNull(request.getAge()), "age", 18) 127 | .lt("birthday", new Date()) 128 | .build(); 129 | 130 | return personRepository.findAll(specification); 131 | } 132 | ``` 133 | 134 | #### Between Example 135 | 136 | find any person age between 18 and 25, birthday between someday and someday. 137 | 138 | **Test:** [BetweenTest.java] 139 | 140 | ```java 141 | public List findAll(SearchRequest request) { 142 | Specification specification = Specifications.and() 143 | .between(Objects.nonNull(request.getAge(), "age", 18, 25) 144 | .between("birthday", new Date(), new Date()) 145 | .build(); 146 | 147 | return personRepository.findAll(specification); 148 | } 149 | ``` 150 | 151 | #### Like/NotLike Example 152 | 153 | find any person name like %ac% or %og%, company not like %ec%. 154 | 155 | **Test:** [LikeTest.java] and [NotLikeTest.java] 156 | 157 | ```java 158 | public Page findAll(SearchRequest request) { 159 | Specification specification = Specifications.and() 160 | .like("name", "ac", "%og%") 161 | .notLike("company", "ec") 162 | .build(); 163 | 164 | return personRepository.findAll(specification); 165 | } 166 | ``` 167 | 168 | #### Or 169 | 170 | support or specifications. 171 | 172 | **Test:** [OrTest.java] 173 | 174 | ```java 175 | public List findAll(SearchRequest request) { 176 | Specification specification = Specifications.or() 177 | .like("name", "%ac%") 178 | .gt("age", 19) 179 | .build(); 180 | 181 | return phoneRepository.findAll(specification); 182 | } 183 | ``` 184 | 185 | #### Join 186 | 187 | each specification support association query as left join. 188 | 189 | **Test:** [JoinTest.java] 190 | 191 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 192 | 193 | ```java 194 | public List findAll(SearchRequest request) { 195 | Specification specification = Specifications.and() 196 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 197 | .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack") 198 | .build(); 199 | 200 | return phoneRepository.findAll(specification); 201 | } 202 | ``` 203 | 204 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 205 | 206 | ```java 207 | public List findAll(SearchRequest request) { 208 | Specification specification = Specifications.and() 209 | .between("age", 10, 35) 210 | .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu") 211 | .build(); 212 | 213 | return phoneRepository.findAll(specification); 214 | } 215 | ``` 216 | 217 | #### Custom Specification 218 | 219 | You can custom specification to do the @ManyToOne and @ManyToMany as well. 220 | 221 | @ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei". 222 | 223 | **Test:** [AndTest.java] 224 | 225 | ```java 226 | public List findAll(SearchRequest request) { 227 | Specification specification = Specifications.and() 228 | .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei") 229 | .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> { 230 | Path person = root.get("person"); 231 | return cb.equal(person.get("name"), "Jack"); 232 | }) 233 | .build(); 234 | 235 | return phoneRepository.findAll(specification); 236 | } 237 | ``` 238 | 239 | @ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street. 240 | 241 | **Test:** [AndTest.java] 242 | 243 | ```java 244 | public List findAll(SearchRequest request) { 245 | Specification specification = Specifications.and() 246 | .between("age", 10, 35) 247 | .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> { 248 | Join address = root.join("addresses", JoinType.LEFT); 249 | return cb.equal(address.get("street"), "Chengdu"); 250 | })) 251 | .build(); 252 | 253 | return phoneRepository.findAll(specification); 254 | } 255 | ``` 256 | 257 | #### Sort 258 | 259 | **Test:** [SortTest.java] 260 | 261 | ```java 262 | public List findAll(SearchRequest request) { 263 | Specification specification = Specifications.and() 264 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 265 | .gt("age", 18) 266 | .between("birthday", new Date(), new Date()) 267 | .like("nickName", "%og%") 268 | .build(); 269 | 270 | Sort sort = Sorts.builder() 271 | .desc(StringUtils.isNotBlank(request.getName()), "name") 272 | .asc("birthday") 273 | .build(); 274 | 275 | return personRepository.findAll(specification, sort); 276 | } 277 | ``` 278 | 279 | #### Pagination 280 | 281 | find person by pagination and sort by name desc and birthday asc. 282 | 283 | ```java 284 | public Page findAll(SearchRequest request) { 285 | Specification specification = Specifications.and() 286 | .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) 287 | .gt("age", 18) 288 | .between("birthday", new Date(), new Date()) 289 | .like("nickName", "%og%") 290 | .build(); 291 | 292 | Sort sort = Sorts.builder() 293 | .desc(StringUtils.isNotBlank(request.getName()), "name") 294 | .asc("birthday") 295 | .build(); 296 | 297 | return personRepository.findAll(specification, new PageRequest(0, 15, sort)); 298 | } 299 | ``` 300 | 301 | #### Virtual View 302 | 303 | Using **@org.hibernate.annotations.Subselect** to define a virtual view if you don't want a database table view. 304 | 305 | There is no difference between a view and a database table for a Hibernate mapping. 306 | 307 | **Test:** [VirtualViewTest.java] 308 | 309 | ```java 310 | @Entity 311 | @Immutable 312 | @Subselect("SELECT p.id, p.name, p.age, ic.number " + 313 | "FROM person p " + 314 | "LEFT JOIN id_card ic " + 315 | "ON p.id_card_id=ic.id") 316 | public class PersonIdCard { 317 | @Id 318 | private Long id; 319 | private String name; 320 | private Integer age; 321 | private String number; 322 | 323 | // Getters and setters are omitted for brevity 324 | } 325 | ``` 326 | 327 | ```java 328 | public List findAll(SearchRequest request) { 329 | Specification specification = Specifications.and() 330 | .gt(Objects.nonNull(request.getAge()), "age", 18) 331 | .build(); 332 | 333 | return personIdCardRepository.findAll(specification); 334 | } 335 | ``` 336 | 337 | #### Projection, GroupBy, Aggregation 338 | 339 | Spring Data JPA doesn't support **Projection**(a little but trick), **GroupBy** and **Aggregation**, 340 | 341 | furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it. 342 | 343 | Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option. 344 | 345 | ### Copyright and license 346 | 347 | Copyright © 2016-2018 Wen Hao 348 | 349 | Licensed under [Apache License] 350 | 351 | [EqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/EqualTest.java 352 | [NotEqualTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java 353 | [InTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/InTest.java 354 | [GtTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/GtTest.java 355 | [BetweenTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java 356 | [LikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/LikeTest.java 357 | [NotLikeTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java 358 | [OrTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/OrTest.java 359 | [AndTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/AndTest.java 360 | [JoinTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/JoinTest.java 361 | [SortTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/SortsTest.java 362 | [VirtualViewTest.java]: ./src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java 363 | [MIT License]: ./LICENSE 364 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## compile 2 | springDataJpaVersion=2.1.6.RELEASE 3 | springBootVersion=2.1.6.RELEASE 4 | jpaVersion=1.0.2.Final 5 | 6 | ## testCompile 7 | commonsLang3Version=3.8.1 8 | assertjVersion = 3.12.1 9 | junitVersion = 5.2.0 10 | mockitoVersion = 2.25.0 11 | h2Version=1.4.198 12 | 13 | ## jcenter 14 | bintrayUser=wenhao 15 | bintrayApiKey=b081f7492fb1970bd47af32c339c1fcf0829dae6 16 | 17 | sonatypeUsername=wenhao 18 | sonatypePassword=1qaz!QAZ 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenhao/jpa-spec/92563ce9cc37305dcd3cb962c7f6a36ebbffa8a9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jpa-spec' 2 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/PredicateBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa; 23 | 24 | import com.github.wenhao.jpa.specification.BetweenSpecification; 25 | import com.github.wenhao.jpa.specification.EqualSpecification; 26 | import com.github.wenhao.jpa.specification.GeSpecification; 27 | import com.github.wenhao.jpa.specification.GtSpecification; 28 | import com.github.wenhao.jpa.specification.InSpecification; 29 | import com.github.wenhao.jpa.specification.LeSpecification; 30 | import com.github.wenhao.jpa.specification.LikeSpecification; 31 | import com.github.wenhao.jpa.specification.LtSpecification; 32 | import com.github.wenhao.jpa.specification.NotEqualSpecification; 33 | import com.github.wenhao.jpa.specification.NotInSpecification; 34 | import com.github.wenhao.jpa.specification.NotLikeSpecification; 35 | import static javax.persistence.criteria.Predicate.BooleanOperator.OR; 36 | import org.springframework.data.jpa.domain.Specification; 37 | 38 | import javax.persistence.criteria.CriteriaBuilder; 39 | import javax.persistence.criteria.CriteriaQuery; 40 | import javax.persistence.criteria.Predicate; 41 | import javax.persistence.criteria.Root; 42 | import java.util.ArrayList; 43 | import java.util.Collection; 44 | import java.util.List; 45 | import java.util.Objects; 46 | 47 | public class PredicateBuilder { 48 | 49 | private final Predicate.BooleanOperator operator; 50 | 51 | private List> specifications; 52 | 53 | public PredicateBuilder(Predicate.BooleanOperator operator) { 54 | this.operator = operator; 55 | this.specifications = new ArrayList<>(); 56 | } 57 | 58 | public PredicateBuilder eq(String property, Object... values) { 59 | return eq(true, property, values); 60 | } 61 | 62 | public PredicateBuilder eq(boolean condition, String property, Object... values) { 63 | return this.predicate(condition, new EqualSpecification(property, values)); 64 | } 65 | 66 | public PredicateBuilder ne(String property, Object... values) { 67 | return ne(true, property, values); 68 | } 69 | 70 | public PredicateBuilder ne(boolean condition, String property, Object... values) { 71 | return this.predicate(condition, new NotEqualSpecification(property, values)); 72 | } 73 | 74 | public PredicateBuilder gt(String property, Comparable compare) { 75 | return gt(true, property, compare); 76 | } 77 | 78 | public PredicateBuilder gt(boolean condition, String property, Comparable compare) { 79 | return this.predicate(condition, new GtSpecification(property, compare)); 80 | } 81 | 82 | public PredicateBuilder ge(String property, Comparable compare) { 83 | return ge(true, property, compare); 84 | } 85 | 86 | public PredicateBuilder ge(boolean condition, String property, Comparable compare) { 87 | return this.predicate(condition, new GeSpecification(property, compare)); 88 | } 89 | 90 | public PredicateBuilder lt(String property, Comparable number) { 91 | return lt(true, property, number); 92 | } 93 | 94 | public PredicateBuilder lt(boolean condition, String property, Comparable compare) { 95 | return this.predicate(condition, new LtSpecification(property, compare)); 96 | } 97 | 98 | public PredicateBuilder le(String property, Comparable compare) { 99 | return le(true, property, compare); 100 | } 101 | 102 | public PredicateBuilder le(boolean condition, String property, Comparable compare) { 103 | return this.predicate(condition, new LeSpecification(property, compare)); 104 | } 105 | 106 | public PredicateBuilder between(String property, Object lower, Object upper) { 107 | return between(true, property, lower, upper); 108 | } 109 | 110 | public PredicateBuilder between(boolean condition, String property, Object lower, Object upper) { 111 | return this.predicate(condition, new BetweenSpecification(property, lower, upper)); 112 | } 113 | 114 | public PredicateBuilder like(String property, String... patterns) { 115 | return like(true, property, patterns); 116 | } 117 | 118 | public PredicateBuilder like(boolean condition, String property, String... patterns) { 119 | return this.predicate(condition, new LikeSpecification(property, patterns)); 120 | } 121 | 122 | public PredicateBuilder notLike(String property, String... patterns) { 123 | return notLike(true, property, patterns); 124 | } 125 | 126 | public PredicateBuilder notLike(boolean condition, String property, String... patterns) { 127 | return this.predicate(condition, new NotLikeSpecification(property, patterns)); 128 | } 129 | 130 | public PredicateBuilder in(String property, Collection values) { 131 | return this.in(true, property, values); 132 | } 133 | 134 | public PredicateBuilder in(boolean condition, String property, Collection values) { 135 | return this.predicate(condition, new InSpecification(property, values)); 136 | } 137 | 138 | public PredicateBuilder notIn(String property, Collection values) { 139 | return this.notIn(true, property, values); 140 | } 141 | 142 | public PredicateBuilder notIn(boolean condition, String property, Collection values) { 143 | return this.predicate(condition, new NotInSpecification(property, values)); 144 | } 145 | 146 | public PredicateBuilder predicate(Specification specification) { 147 | return predicate(true, specification); 148 | } 149 | 150 | public PredicateBuilder predicate(boolean condition, Specification specification) { 151 | if (condition) { 152 | this.specifications.add(specification); 153 | } 154 | return this; 155 | } 156 | 157 | public Specification build() { 158 | return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { 159 | Predicate[] predicates = new Predicate[specifications.size()]; 160 | for (int i = 0; i < specifications.size(); i++) { 161 | predicates[i] = specifications.get(i).toPredicate(root, query, cb); 162 | } 163 | if (Objects.equals(predicates.length, 0)) { 164 | return null; 165 | } 166 | return OR.equals(operator) ? cb.or(predicates) : cb.and(predicates); 167 | }; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/Sorts.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa; 23 | 24 | import org.springframework.data.domain.Sort; 25 | import org.springframework.data.domain.Sort.Order; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | import static org.springframework.data.domain.Sort.Direction.ASC; 31 | import static org.springframework.data.domain.Sort.Direction.DESC; 32 | 33 | public final class Sorts { 34 | 35 | private Sorts() { 36 | } 37 | 38 | public static Builder builder() { 39 | return new Builder(); 40 | } 41 | 42 | public static final class Builder { 43 | private List orders; 44 | 45 | public Builder() { 46 | this.orders = new ArrayList<>(); 47 | } 48 | 49 | public Builder asc(String property) { 50 | return asc(true, property); 51 | } 52 | 53 | public Builder desc(String property) { 54 | return desc(true, property); 55 | } 56 | 57 | public Builder asc(boolean condition, String property) { 58 | if (condition) { 59 | orders.add(new Order(ASC, property)); 60 | } 61 | return this; 62 | } 63 | 64 | public Builder desc(boolean condition, String property) { 65 | if (condition) { 66 | orders.add(new Order(DESC, property)); 67 | } 68 | return this; 69 | } 70 | 71 | public Sort build() { 72 | return Sort.by(orders); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/Specifications.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa; 23 | 24 | import static javax.persistence.criteria.Predicate.BooleanOperator.AND; 25 | import static javax.persistence.criteria.Predicate.BooleanOperator.OR; 26 | 27 | public final class Specifications { 28 | 29 | private Specifications() { 30 | } 31 | 32 | public static PredicateBuilder and() { 33 | return new PredicateBuilder<>(AND); 34 | } 35 | public static PredicateBuilder or() { 36 | return new PredicateBuilder<>(OR); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/AbstractSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import org.springframework.data.jpa.domain.Specification; 25 | import org.springframework.util.StringUtils; 26 | 27 | import java.io.Serializable; 28 | 29 | import javax.persistence.criteria.From; 30 | import javax.persistence.criteria.JoinType; 31 | import javax.persistence.criteria.Root; 32 | 33 | abstract class AbstractSpecification implements Specification, Serializable { 34 | public String getProperty(String property) { 35 | if (property.contains(".")) { 36 | return StringUtils.split(property, ".")[1]; 37 | } 38 | return property; 39 | } 40 | 41 | public From getRoot(String property, Root root) { 42 | if (property.contains(".")) { 43 | String joinProperty = StringUtils.split(property, ".")[0]; 44 | return root.join(joinProperty, JoinType.LEFT); 45 | } 46 | return root; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/BetweenSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | 25 | import javax.persistence.criteria.CriteriaBuilder; 26 | import javax.persistence.criteria.CriteriaQuery; 27 | import javax.persistence.criteria.From; 28 | import javax.persistence.criteria.Predicate; 29 | import javax.persistence.criteria.Root; 30 | 31 | 32 | public class BetweenSpecification extends AbstractSpecification { 33 | private final String property; 34 | private final transient Comparable lower; 35 | private final transient Comparable upper; 36 | 37 | public BetweenSpecification(String property, Object lower, Object upper) { 38 | this.property = property; 39 | this.lower = (Comparable) lower; 40 | this.upper = (Comparable) upper; 41 | } 42 | 43 | @Override 44 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 45 | From from = getRoot(property, root); 46 | String field = getProperty(property); 47 | return cb.between(from.get(field), lower, upper); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/EqualSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class EqualSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Object[] values; 33 | 34 | public EqualSpecification(String property, Object... values) { 35 | this.property = property; 36 | this.values = values; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | if (values == null) { 44 | return cb.isNull(from.get(field)); 45 | } 46 | if (values.length == 1) { 47 | return getPredicate(from, cb, values[0], field); 48 | } 49 | 50 | Predicate[] predicates = new Predicate[values.length]; 51 | for (int i = 0; i < values.length; i++) { 52 | predicates[i] = getPredicate(root, cb, values[i], field); 53 | } 54 | return cb.or(predicates); 55 | } 56 | 57 | private Predicate getPredicate(From root, CriteriaBuilder cb, Object value, String field) { 58 | return value == null ? cb.isNull(root.get(field)) : cb.equal(root.get(field), value); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/GeSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class GeSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Comparable compare; 33 | 34 | public GeSpecification(String property, Comparable compare) { 35 | this.property = property; 36 | this.compare = (Comparable) compare; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | return cb.greaterThanOrEqualTo(from.get(field), compare); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/GtSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class GtSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Comparable compare; 33 | 34 | public GtSpecification(String property, Comparable compare) { 35 | this.property = property; 36 | this.compare = (Comparable) compare; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | return cb.greaterThan(from.get(field), compare); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/InSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | import java.util.Collection; 30 | 31 | public class InSpecification extends AbstractSpecification { 32 | private final String property; 33 | private final transient Collection values; 34 | 35 | public InSpecification(String property, Collection values) { 36 | this.property = property; 37 | this.values = values; 38 | } 39 | 40 | @Override 41 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 42 | From from = getRoot(property, root); 43 | String field = getProperty(property); 44 | return from.get(field).in(values); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/LeSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class LeSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Comparable compare; 33 | 34 | public LeSpecification(String property, Comparable compare) { 35 | this.property = property; 36 | this.compare = (Comparable) compare; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | return cb.lessThanOrEqualTo(from.get(field), compare); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/LikeSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class LikeSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final String[] patterns; 33 | 34 | public LikeSpecification(String property, String... patterns) { 35 | this.property = property; 36 | this.patterns = patterns; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | if (patterns.length == 1) { 44 | return cb.like(from.get(field), patterns[0]); 45 | } 46 | Predicate[] predicates = new Predicate[patterns.length]; 47 | for (int i = 0; i < patterns.length; i++) { 48 | predicates[i] = cb.like(from.get(field), patterns[i]); 49 | } 50 | return cb.or(predicates); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/LtSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class LtSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Comparable compare; 33 | 34 | public LtSpecification(String property, Comparable compare) { 35 | this.property = property; 36 | this.compare = (Comparable) compare; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | return cb.lessThan(from.get(field), compare); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/NotEqualSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class NotEqualSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final transient Object[] values; 33 | 34 | public NotEqualSpecification(String property, Object... values) { 35 | this.property = property; 36 | this.values = values; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | if (values == null) { 44 | return cb.isNotNull(from.get(field)); 45 | } 46 | if (values.length == 1) { 47 | return getPredicate(from, cb, values[0], field); 48 | } 49 | Predicate[] predicates = new Predicate[values.length]; 50 | for (int i = 0; i < values.length; i++) { 51 | predicates[i] = getPredicate(root, cb, values[i], field); 52 | } 53 | return cb.or(predicates); 54 | } 55 | 56 | private Predicate getPredicate(From root, CriteriaBuilder cb, Object value, String field) { 57 | return value == null ? cb.isNotNull(root.get(field)) : cb.notEqual(root.get(field), value); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/NotInSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | import java.util.Collection; 30 | 31 | public class NotInSpecification extends AbstractSpecification { 32 | private final String property; 33 | private final transient Collection values; 34 | 35 | public NotInSpecification(String property, Collection values) { 36 | this.property = property; 37 | this.values = values; 38 | } 39 | 40 | @Override 41 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 42 | From from = getRoot(property, root); 43 | String field = getProperty(property); 44 | return from.get(field).in(values).not(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/wenhao/jpa/specification/NotLikeSpecification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.specification; 23 | 24 | import javax.persistence.criteria.CriteriaBuilder; 25 | import javax.persistence.criteria.CriteriaQuery; 26 | import javax.persistence.criteria.From; 27 | import javax.persistence.criteria.Predicate; 28 | import javax.persistence.criteria.Root; 29 | 30 | public class NotLikeSpecification extends AbstractSpecification { 31 | private final String property; 32 | private final String[] patterns; 33 | 34 | public NotLikeSpecification(String property, String... patterns) { 35 | this.property = property; 36 | this.patterns = patterns; 37 | } 38 | 39 | @Override 40 | public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { 41 | From from = getRoot(property, root); 42 | String field = getProperty(property); 43 | if (patterns.length == 1) { 44 | return cb.like(from.get(field), patterns[0]).not(); 45 | } 46 | Predicate[] predicates = new Predicate[patterns.length]; 47 | for (int i = 0; i < patterns.length; i++) { 48 | predicates[i] = cb.like(from.get(field), patterns[i]).not(); 49 | } 50 | return cb.or(predicates); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/Application.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa; 23 | 24 | import org.springframework.boot.SpringApplication; 25 | import org.springframework.boot.autoconfigure.SpringBootApplication; 26 | 27 | @SpringBootApplication 28 | public class Application { 29 | 30 | public static void main(String[] args) { 31 | SpringApplication.run(Application.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/builder/PersonBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.builder; 23 | 24 | import com.github.wenhao.jpa.model.Address; 25 | import com.github.wenhao.jpa.model.IdCard; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.model.Phone; 28 | 29 | import java.util.Date; 30 | 31 | public class PersonBuilder { 32 | private Person person; 33 | 34 | public PersonBuilder() { 35 | this.person = new Person(); 36 | } 37 | 38 | public PersonBuilder name(String name) { 39 | this.person.setName(name); 40 | return this; 41 | } 42 | 43 | public PersonBuilder age(Integer age) { 44 | this.person.setAge(age); 45 | return this; 46 | } 47 | 48 | public PersonBuilder nickName(String nickName) { 49 | this.person.setNickName(nickName); 50 | return this; 51 | } 52 | 53 | public PersonBuilder company(String company) { 54 | this.person.setCompany(company); 55 | return this; 56 | } 57 | 58 | public PersonBuilder birthday(Date birthday) { 59 | this.person.setBirthday(birthday); 60 | return this; 61 | } 62 | 63 | public PersonBuilder phone(String brand, String number) { 64 | Phone phone = new Phone(); 65 | phone.setBrand(brand); 66 | phone.setNumber(number); 67 | this.person.getPhones().add(phone); 68 | return this; 69 | } 70 | 71 | public PersonBuilder address(String street, Integer number) { 72 | Address address = new Address(); 73 | address.setStreet(street); 74 | address.setNumber(number); 75 | this.person.getAddresses().add(address); 76 | return this; 77 | } 78 | 79 | public PersonBuilder idCard(String number) { 80 | IdCard idCard = new IdCard(); 81 | idCard.setNumber(number); 82 | this.person.setIdCard(idCard); 83 | return this; 84 | } 85 | 86 | public PersonBuilder married(boolean married) { 87 | this.person.setMarried(married); 88 | return this; 89 | } 90 | 91 | public Person build() { 92 | return this.person; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/AndOrTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 32 | import org.springframework.data.jpa.domain.Specification; 33 | 34 | import java.util.List; 35 | 36 | @DataJpaTest 37 | public class AndOrTest { 38 | 39 | @Autowired 40 | private PersonRepository personRepository; 41 | 42 | @Test 43 | public void should_be_able_to_find_by_using_and_or() { 44 | // given 45 | Person jack = new PersonBuilder() 46 | .name("Jack") 47 | .age(18) 48 | .build(); 49 | Person eric = new PersonBuilder() 50 | .name("Eric") 51 | .age(20) 52 | .build(); 53 | Person jackson = new PersonBuilder() 54 | .age(30) 55 | .name("Jackson") 56 | .build(); 57 | personRepository.save(jack); 58 | personRepository.save(eric); 59 | personRepository.save(jackson); 60 | 61 | // when 62 | Specification specification = Specifications.and() 63 | .like("name", "%ac%") 64 | .predicate(Specifications.or() 65 | .lt("age", 19) 66 | .gt("age", 25) 67 | .build()) 68 | .build(); 69 | 70 | List persons = personRepository.findAll(specification); 71 | 72 | // then 73 | assertThat(persons.size()).isEqualTo(2); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/BetweenTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static java.util.Objects.nonNull; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.text.ParseException; 36 | import java.text.SimpleDateFormat; 37 | import java.util.Date; 38 | import java.util.List; 39 | 40 | @DataJpaTest 41 | public class BetweenTest { 42 | 43 | @Autowired 44 | private PersonRepository personRepository; 45 | 46 | @Test 47 | public void should_be_able_to_find_by_using_between() throws ParseException { 48 | // given 49 | Person jack = new PersonBuilder() 50 | .name("Jack") 51 | .birthday(getDate("1987-11-14")) 52 | .build(); 53 | Person eric = new PersonBuilder() 54 | .name("Eric") 55 | .birthday(getDate("1990-10-12")) 56 | .build(); 57 | personRepository.save(jack); 58 | personRepository.save(eric); 59 | 60 | // when 61 | Specification specification = Specifications.and() 62 | .between(nonNull(jack.getBirthday()), "birthday", getDate("1980-01-01"), getDate("1989-12-31")) 63 | .build(); 64 | 65 | List persons = personRepository.findAll(specification); 66 | 67 | // then 68 | assertThat(persons.size()).isEqualTo(1); 69 | } 70 | 71 | private Date getDate(String source) throws ParseException { 72 | return new SimpleDateFormat("yyyy-MM-dd").parse(source); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/EqualTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.EMPTY; 29 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | import org.junit.jupiter.api.Test; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 34 | import org.springframework.data.jpa.domain.Specification; 35 | 36 | import java.util.List; 37 | import java.util.Optional; 38 | 39 | @DataJpaTest 40 | public class EqualTest { 41 | 42 | @Autowired 43 | private PersonRepository personRepository; 44 | 45 | @Test 46 | public void should_be_able_to_find_by_using_equal() { 47 | // given 48 | Person person = new PersonBuilder() 49 | .name("Jack") 50 | .age(18) 51 | .married(true) 52 | .build(); 53 | personRepository.save(person); 54 | 55 | // when 56 | Specification specification = Specifications.and() 57 | .eq(isNotBlank(person.getName()), "name", person.getName()) 58 | .eq("married", Boolean.TRUE) 59 | .build(); 60 | 61 | Optional result = personRepository.findOne(specification); 62 | 63 | // then 64 | assertThat(result.get().getName()).isEqualTo(person.getName()); 65 | } 66 | 67 | @SuppressWarnings("all") 68 | @Test 69 | public void should_be_able_to_find_by_using_equal_for_single_null_value() { 70 | // given 71 | Person jack = new PersonBuilder() 72 | .name("Jack") 73 | .age(18) 74 | .company("Abc") 75 | .build(); 76 | Person eric = new PersonBuilder() 77 | .name("Eric") 78 | .age(20) 79 | .build(); 80 | 81 | personRepository.save(jack); 82 | personRepository.save(eric); 83 | 84 | // when 85 | Specification specification = Specifications.and() 86 | .eq("company", (Object) null) 87 | .eq("nickName", null) 88 | .build(); 89 | 90 | List persons = personRepository.findAll(specification); 91 | 92 | // then 93 | assertThat(persons.size()).isEqualTo(1); 94 | } 95 | 96 | @Test 97 | public void should_be_able_to_find_by_using_equal_with_multiple_values() { 98 | // given 99 | Person jack = new PersonBuilder() 100 | .name("Jack") 101 | .age(18) 102 | .build(); 103 | Person eric = new PersonBuilder() 104 | .name("Eric") 105 | .age(20) 106 | .build(); 107 | Person jackson = new PersonBuilder() 108 | .age(30) 109 | .nickName("Jackson") 110 | .build(); 111 | personRepository.save(jack); 112 | personRepository.save(eric); 113 | personRepository.save(jackson); 114 | 115 | // when 116 | Specification specification = Specifications.and() 117 | .eq(isNotBlank(jack.getName()), "name", jack.getName(), eric.getName(), null) 118 | .build(); 119 | 120 | List persons = personRepository.findAll(specification); 121 | 122 | // then 123 | assertThat(persons.size()).isEqualTo(3); 124 | } 125 | 126 | @Test 127 | public void should_be_able_to_find_all_if_all_predicate_are_null() { 128 | // given 129 | Person jack = new PersonBuilder() 130 | .name("Jack") 131 | .age(18) 132 | .build(); 133 | Person eric = new PersonBuilder() 134 | .name("Eric") 135 | .age(20) 136 | .build(); 137 | personRepository.save(jack); 138 | personRepository.save(eric); 139 | 140 | // when 141 | Specification specification = Specifications.and() 142 | .eq(isNotBlank(EMPTY), "name", jack.getName()) 143 | .like(isNotBlank(EMPTY), "name", "%" + jack.getName() + "%") 144 | .build(); 145 | 146 | List persons = personRepository.findAll(specification); 147 | 148 | // then 149 | assertThat(persons.size()).isEqualTo(2); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/GreatEqualTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 32 | import org.springframework.data.jpa.domain.Specification; 33 | 34 | import java.util.List; 35 | 36 | @DataJpaTest 37 | public class GreatEqualTest { 38 | 39 | @Autowired 40 | private PersonRepository personRepository; 41 | 42 | @Test 43 | public void should_be_able_to_find_by_using_great_equal() { 44 | // given 45 | Person jack = new PersonBuilder() 46 | .name("Jack") 47 | .age(20) 48 | .build(); 49 | Person eric = new PersonBuilder() 50 | .name("Eric") 51 | .age(18) 52 | .build(); 53 | personRepository.save(jack); 54 | personRepository.save(eric); 55 | 56 | // when 57 | Specification specification = Specifications.and() 58 | .ge("age", 20) 59 | .build(); 60 | 61 | List persons = personRepository.findAll(specification); 62 | 63 | // then 64 | assertThat(persons.size()).isEqualTo(1); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/GreatThanTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 32 | import org.springframework.data.jpa.domain.Specification; 33 | 34 | import java.util.List; 35 | 36 | @DataJpaTest 37 | public class GreatThanTest { 38 | 39 | @Autowired 40 | private PersonRepository personRepository; 41 | 42 | @Test 43 | public void should_be_able_to_find_by_using_great_than() { 44 | // given 45 | Person jack = new PersonBuilder() 46 | .name("Jack") 47 | .age(20) 48 | .build(); 49 | Person eric = new PersonBuilder() 50 | .name("Eric") 51 | .age(18) 52 | .build(); 53 | personRepository.save(jack); 54 | personRepository.save(eric); 55 | 56 | // when 57 | Specification specification = Specifications.and() 58 | .gt("age", 18) 59 | .build(); 60 | 61 | List persons = personRepository.findAll(specification); 62 | 63 | // then 64 | assertThat(persons.size()).isEqualTo(1); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/InTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.util.Arrays; 36 | import java.util.List; 37 | 38 | @DataJpaTest 39 | public class InTest { 40 | 41 | @Autowired 42 | private PersonRepository personRepository; 43 | 44 | @Test 45 | public void should_be_able_to_find_by_using_in() { 46 | // given 47 | Person jack = new PersonBuilder() 48 | .name("Jack") 49 | .nickName("Jack") 50 | .age(18) 51 | .build(); 52 | Person eric = new PersonBuilder() 53 | .name("Eric") 54 | .nickName("Eric") 55 | .age(20) 56 | .build(); 57 | personRepository.save(jack); 58 | personRepository.save(eric); 59 | 60 | // when 61 | Specification specification = Specifications.and() 62 | .in(isNotBlank(jack.getName()), "name", Arrays.asList("Jack", "Eric")) 63 | .in("name", Arrays.asList("Jack", "Eric")) 64 | .build(); 65 | 66 | List persons = personRepository.findAll(specification); 67 | 68 | // then 69 | assertThat(persons.size()).isEqualTo(2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/JoinTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.model.Phone; 28 | import com.github.wenhao.jpa.repository.PersonRepository; 29 | import com.github.wenhao.jpa.repository.PhoneRepository; 30 | import org.apache.commons.lang3.StringUtils; 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import org.junit.jupiter.api.Test; 33 | import org.springframework.beans.factory.annotation.Autowired; 34 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 35 | import org.springframework.data.jpa.domain.Specification; 36 | 37 | import java.util.List; 38 | import java.util.Set; 39 | 40 | @DataJpaTest 41 | public class JoinTest { 42 | 43 | @Autowired 44 | private PersonRepository personRepository; 45 | @Autowired 46 | private PhoneRepository phoneRepository; 47 | 48 | @Test 49 | public void should_be_able_to_find_by_using_many_to_one_query() { 50 | // given 51 | Person jack = new PersonBuilder() 52 | .name("Jack") 53 | .age(18) 54 | .phone("iPhone", "139000000000") 55 | .phone("HuaWei", "13600000000") 56 | .phone("HuaWei", "18000000000") 57 | .phone("Samsung", "13600000000") 58 | .build(); 59 | 60 | Set jackPhones = jack.getPhones(); 61 | for (Phone phone : jackPhones) { 62 | phone.setPerson(jack); 63 | } 64 | personRepository.save(jack); 65 | 66 | // when 67 | Specification specification = Specifications.and() 68 | .eq("brand", "HuaWei") 69 | .eq(StringUtils.isNotBlank(jack.getName()), "person.name", jack.getName()) 70 | .build(); 71 | 72 | List phones = phoneRepository.findAll(specification); 73 | 74 | // then 75 | assertThat(phones.size()).isEqualTo(2); 76 | } 77 | 78 | @Test 79 | public void should_be_able_to_find_by_using_many_to_many_query() { 80 | // given 81 | Person jack = new PersonBuilder() 82 | .name("Jack") 83 | .age(18) 84 | .address("Sichuan", 3) 85 | .address("Sichuan", 5) 86 | .address("Chengdu", 4) 87 | .address("Zhonghe", 7) 88 | .build(); 89 | 90 | Person eric = new PersonBuilder() 91 | .name("Eric") 92 | .age(20) 93 | .address("GaoXin", 8) 94 | .address("Tianfu", 9) 95 | .address("Chengdu", 4) 96 | .build(); 97 | 98 | Person alex = new PersonBuilder() 99 | .name("Alex") 100 | .age(30) 101 | .address("HuaYang", 1) 102 | .address("NeiJiang", 2) 103 | .build(); 104 | 105 | personRepository.save(jack); 106 | personRepository.save(eric); 107 | personRepository.save(alex); 108 | 109 | // when 110 | Specification specification = Specifications.and() 111 | .between("age", 10, 35) 112 | .eq("addresses.street", "Chengdu") 113 | .build(); 114 | 115 | List phones = personRepository.findAll(specification); 116 | 117 | // then 118 | assertThat(phones.size()).isEqualTo(2); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/LessEqualTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 32 | import org.springframework.data.jpa.domain.Specification; 33 | 34 | import java.util.List; 35 | 36 | @DataJpaTest 37 | public class LessEqualTest { 38 | 39 | @Autowired 40 | private PersonRepository personRepository; 41 | 42 | @Test 43 | public void should_be_able_to_find_by_using_less_equal() { 44 | // given 45 | Person jack = new PersonBuilder() 46 | .name("Jack") 47 | .age(20) 48 | .build(); 49 | Person eric = new PersonBuilder() 50 | .name("Eric") 51 | .age(18) 52 | .build(); 53 | personRepository.save(jack); 54 | personRepository.save(eric); 55 | 56 | // when 57 | Specification specification = Specifications.and() 58 | .le("age", 18) 59 | .build(); 60 | 61 | List persons = personRepository.findAll(specification); 62 | 63 | // then 64 | assertThat(persons.size()).isEqualTo(1); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/LessThanTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import org.junit.jupiter.api.Test; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 32 | import org.springframework.data.jpa.domain.Specification; 33 | 34 | import java.util.List; 35 | 36 | @DataJpaTest 37 | public class LessThanTest { 38 | 39 | @Autowired 40 | private PersonRepository personRepository; 41 | 42 | @Test 43 | public void should_be_able_to_find_by_using_less_than() { 44 | // given 45 | Person jack = new PersonBuilder() 46 | .name("Jack") 47 | .age(20) 48 | .build(); 49 | Person eric = new PersonBuilder() 50 | .name("Eric") 51 | .age(18) 52 | .build(); 53 | personRepository.save(jack); 54 | personRepository.save(eric); 55 | 56 | // when 57 | Specification specification = Specifications.and() 58 | .lt("age", 20) 59 | .build(); 60 | 61 | List persons = personRepository.findAll(specification); 62 | 63 | // then 64 | assertThat(persons.size()).isEqualTo(1); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/LikeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.util.List; 36 | 37 | @DataJpaTest 38 | public class LikeTest { 39 | 40 | @Autowired 41 | private PersonRepository personRepository; 42 | 43 | @Test 44 | public void should_be_able_to_find_by_using_like_with_multiple_values() { 45 | // given 46 | Person jack = new PersonBuilder() 47 | .name("Jack") 48 | .age(18) 49 | .build(); 50 | Person eric = new PersonBuilder() 51 | .name("Eric") 52 | .age(20) 53 | .build(); 54 | personRepository.save(jack); 55 | personRepository.save(eric); 56 | 57 | // when 58 | Specification specification = Specifications.and() 59 | .like(isNotBlank(jack.getName()), "name", "%ac%", "%ri%") 60 | .build(); 61 | 62 | List persons = personRepository.findAll(specification); 63 | 64 | // then 65 | assertThat(persons.size()).isEqualTo(2); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/NotEqualTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import org.apache.commons.lang3.StringUtils; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.util.List; 36 | import java.util.Optional; 37 | 38 | @DataJpaTest 39 | public class NotEqualTest { 40 | 41 | @Autowired 42 | private PersonRepository personRepository; 43 | 44 | @Test 45 | public void should_be_able_to_find_by_using_not_equal() { 46 | // given 47 | Person jack = new PersonBuilder() 48 | .name("Jack") 49 | .nickName("Dog") 50 | .company("company-name") 51 | .build(); 52 | Person eric = new PersonBuilder() 53 | .name("Eric") 54 | .nickName("Cat") 55 | .company("company-name") 56 | .build(); 57 | personRepository.save(jack); 58 | personRepository.save(eric); 59 | 60 | // when 61 | Specification specification = Specifications.and() 62 | .eq("company", "company-name") 63 | .ne(StringUtils.isNotBlank(jack.getName()), "name", jack.getName()) 64 | .ne("nickName", jack.getNickName(), "Aaron") 65 | .build(); 66 | 67 | Optional person = personRepository.findOne(specification); 68 | 69 | // then 70 | assertThat(person.get().getName()).isEqualTo(eric.getName()); 71 | } 72 | 73 | @SuppressWarnings("all") 74 | @Test 75 | public void should_be_able_to_find_by_using_not_equal_for_single_null_value() { 76 | // given 77 | Person jack = new PersonBuilder() 78 | .name("Jack") 79 | .age(18) 80 | .company("Abc") 81 | .build(); 82 | Person eric = new PersonBuilder() 83 | .name("Eric") 84 | .age(20) 85 | .build(); 86 | 87 | personRepository.save(jack); 88 | personRepository.save(eric); 89 | 90 | // when 91 | Specification specification = Specifications.and() 92 | .ne("name", null) 93 | .ne("company", (Object) null) 94 | .build(); 95 | 96 | List persons = personRepository.findAll(specification); 97 | 98 | // then 99 | assertThat(persons.size()).isEqualTo(1); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/NotInTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.util.Arrays; 36 | import java.util.List; 37 | 38 | @DataJpaTest 39 | public class NotInTest { 40 | 41 | @Autowired 42 | private PersonRepository personRepository; 43 | 44 | @Test 45 | public void should_be_able_to_find_by_using_in() { 46 | // given 47 | Person jack = new PersonBuilder() 48 | .name("Jack") 49 | .nickName("Jack") 50 | .age(18) 51 | .build(); 52 | Person eric = new PersonBuilder() 53 | .name("Eric") 54 | .nickName("Eric") 55 | .age(20) 56 | .build(); 57 | personRepository.save(jack); 58 | personRepository.save(eric); 59 | 60 | // when 61 | Specification specification = Specifications.and() 62 | .notIn(isNotBlank(jack.getName()), "name", Arrays.asList("Eric")) 63 | .notIn("nickName", Arrays.asList("Eric")) 64 | .build(); 65 | 66 | List persons = personRepository.findAll(specification); 67 | 68 | // then 69 | assertThat(persons.size()).isEqualTo(1); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/NotLikeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.jpa.domain.Specification; 34 | 35 | import java.util.List; 36 | 37 | @DataJpaTest 38 | public class NotLikeTest { 39 | 40 | @Autowired 41 | private PersonRepository personRepository; 42 | 43 | @Test 44 | public void should_be_able_to_find_by_using_not_like() { 45 | // given 46 | Person jack = new PersonBuilder() 47 | .name("Jack") 48 | .age(18) 49 | .nickName("Dog") 50 | .build(); 51 | Person eric = new PersonBuilder() 52 | .name("Eric") 53 | .nickName("Cat") 54 | .age(20) 55 | .build(); 56 | personRepository.save(jack); 57 | personRepository.save(eric); 58 | 59 | // when 60 | Specification specification = Specifications.and() 61 | .notLike(isNotBlank(jack.getName()), "name", "%ac%") 62 | .notLike("name", "%og%", "%ri%") 63 | .build(); 64 | 65 | List persons = personRepository.findAll(specification); 66 | 67 | // then 68 | assertThat(persons.size()).isEqualTo(1); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/OrTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.repository.PersonRepository; 28 | import static org.apache.commons.lang3.StringUtils.EMPTY; 29 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | import org.junit.jupiter.api.Test; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 34 | import org.springframework.data.jpa.domain.Specification; 35 | 36 | import java.util.Date; 37 | import java.util.List; 38 | 39 | @DataJpaTest 40 | public class OrTest { 41 | 42 | @Autowired 43 | private PersonRepository personRepository; 44 | 45 | @Test 46 | public void should_be_able_to_find_by_using_or_with_multiple_values() { 47 | // given 48 | Person jack = new PersonBuilder() 49 | .name("Jack") 50 | .age(18) 51 | .build(); 52 | Person eric = new PersonBuilder() 53 | .name("Eric") 54 | .age(20) 55 | .build(); 56 | Person jackson = new PersonBuilder() 57 | .age(30) 58 | .nickName("Jackson") 59 | .build(); 60 | personRepository.save(jack); 61 | personRepository.save(eric); 62 | personRepository.save(jackson); 63 | 64 | // when 65 | Specification specification = Specifications.or() 66 | .like("name", "%ac%") 67 | .gt("age", 19) 68 | .eq(jack.getCompany() != null, null) 69 | .ne(jack.getNickName() != null, null) 70 | .between(jack.getBirthday() != null, "birthday", new Date(), new Date()) 71 | .build(); 72 | 73 | List persons = personRepository.findAll(specification); 74 | 75 | // then 76 | assertThat(persons.size()).isEqualTo(3); 77 | } 78 | 79 | @Test 80 | public void should_be_able_to_find_all_if_all_predicate_are_null() { 81 | // given 82 | Person jack = new PersonBuilder() 83 | .name("Jack") 84 | .age(18) 85 | .build(); 86 | Person eric = new PersonBuilder() 87 | .name("Eric") 88 | .age(20) 89 | .build(); 90 | personRepository.save(jack); 91 | personRepository.save(eric); 92 | 93 | // when 94 | Specification specification = Specifications.or() 95 | .eq(isNotBlank(EMPTY), "name", jack.getName()) 96 | .like(isNotBlank(EMPTY), "name", "%" + jack.getName() + "%") 97 | .build(); 98 | 99 | List persons = personRepository.findAll(specification); 100 | 101 | // then 102 | assertThat(persons.size()).isEqualTo(2); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/PredicateTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.model.Phone; 28 | import com.github.wenhao.jpa.repository.PersonRepository; 29 | import com.github.wenhao.jpa.repository.PhoneRepository; 30 | import org.apache.commons.lang3.StringUtils; 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import org.junit.jupiter.api.Test; 33 | import org.springframework.beans.factory.annotation.Autowired; 34 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 35 | import org.springframework.data.jpa.domain.Specification; 36 | 37 | import javax.persistence.criteria.Join; 38 | import javax.persistence.criteria.JoinType; 39 | import javax.persistence.criteria.Path; 40 | import java.util.List; 41 | import java.util.Set; 42 | 43 | @DataJpaTest 44 | public class PredicateTest { 45 | 46 | @Autowired 47 | private PersonRepository personRepository; 48 | @Autowired 49 | private PhoneRepository phoneRepository; 50 | 51 | @Test 52 | public void should_be_able_to_find_by_using_many_to_one_query() { 53 | // given 54 | final Person jack = new PersonBuilder() 55 | .name("Jack") 56 | .age(18) 57 | .phone("iPhone", "139000000000") 58 | .phone("HuaWei", "13600000000") 59 | .phone("HuaWei", "18000000000") 60 | .phone("Samsung", "13600000000") 61 | .build(); 62 | 63 | Set jackPhones = jack.getPhones(); 64 | for (Phone phone : jackPhones) { 65 | phone.setPerson(jack); 66 | } 67 | personRepository.save(jack); 68 | 69 | 70 | // when 71 | Specification specification = Specifications.and() 72 | .eq("brand", "HuaWei") 73 | .predicate(StringUtils.isNotBlank(jack.getName()), (Specification) (root, query, cb) -> { 74 | Path person = root.get("person"); 75 | return cb.equal(person.get("name"), jack.getName()); 76 | }) 77 | .build(); 78 | 79 | List phones = phoneRepository.findAll(specification); 80 | 81 | // then 82 | assertThat(phones.size()).isEqualTo(2); 83 | } 84 | 85 | @Test 86 | public void should_be_able_to_find_by_using_many_to_many_query() { 87 | // given 88 | Person jack = new PersonBuilder() 89 | .name("Jack") 90 | .age(18) 91 | .address("Sichuan", 3) 92 | .address("Sichuan", 5) 93 | .address("Chengdu", 4) 94 | .address("Zhonghe", 7) 95 | .build(); 96 | 97 | Person eric = new PersonBuilder() 98 | .name("Eric") 99 | .age(20) 100 | .address("GaoXin", 8) 101 | .address("Tianfu", 9) 102 | .address("Chengdu", 4) 103 | .build(); 104 | 105 | Person alex = new PersonBuilder() 106 | .name("Alex") 107 | .age(30) 108 | .address("HuaYang", 1) 109 | .address("NeiJiang", 2) 110 | .build(); 111 | 112 | personRepository.save(jack); 113 | personRepository.save(eric); 114 | personRepository.save(alex); 115 | 116 | // when 117 | Specification specification = Specifications.and() 118 | .between("age", 10, 35) 119 | .predicate(StringUtils.isNotBlank(jack.getName()), (Specification) (root, query, cb) -> { 120 | Join address = root.join("addresses", JoinType.LEFT); 121 | return cb.equal(address.get("street"), "Chengdu"); 122 | }) 123 | .build(); 124 | 125 | 126 | List phones = personRepository.findAll(specification); 127 | 128 | // then 129 | assertThat(phones.size()).isEqualTo(2); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/SortsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Sorts; 25 | import com.github.wenhao.jpa.Specifications; 26 | import com.github.wenhao.jpa.builder.PersonBuilder; 27 | import com.github.wenhao.jpa.model.Person; 28 | import com.github.wenhao.jpa.repository.PersonRepository; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import org.junit.jupiter.api.Test; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 33 | import org.springframework.data.domain.Sort; 34 | import org.springframework.data.jpa.domain.Specification; 35 | 36 | import java.util.List; 37 | 38 | @DataJpaTest 39 | public class SortsTest { 40 | 41 | @Autowired 42 | private PersonRepository personRepository; 43 | 44 | @Test 45 | public void should_be_able_to_sort_by_desc() { 46 | // given 47 | Person jack = new PersonBuilder() 48 | .name("Jack") 49 | .age(18) 50 | .build(); 51 | Person eric = new PersonBuilder() 52 | .name("Eric") 53 | .age(20) 54 | .build(); 55 | Person aaron = new PersonBuilder() 56 | .name("Aaron") 57 | .age(18) 58 | .build(); 59 | personRepository.save(jack); 60 | personRepository.save(eric); 61 | personRepository.save(aaron); 62 | 63 | // when 64 | Specification specification = Specifications.and() 65 | .ne("name", (Object) null) 66 | .build(); 67 | 68 | Sort sort = Sorts.builder() 69 | .desc(jack.getAge() != null, "age") 70 | .desc("name") 71 | .desc(jack.getCompany() != null, "company") 72 | .build(); 73 | 74 | List persons = personRepository.findAll(specification, sort); 75 | 76 | // then 77 | assertThat(persons.get(0)).isEqualToComparingFieldByField(eric); 78 | assertThat(persons.get(1)).isEqualToComparingFieldByField(jack); 79 | } 80 | 81 | @Test 82 | public void should_be_able_to_sort_by_asc() { 83 | // given 84 | Person jack = new PersonBuilder() 85 | .name("Jack") 86 | .age(18) 87 | .build(); 88 | Person eric = new PersonBuilder() 89 | .name("Eric") 90 | .age(20) 91 | .build(); 92 | Person aaron = new PersonBuilder() 93 | .name("Aaron") 94 | .age(18) 95 | .build(); 96 | personRepository.save(jack); 97 | personRepository.save(eric); 98 | personRepository.save(aaron); 99 | 100 | // when 101 | Specification specification = Specifications.and() 102 | .ne("name", (Object) null) 103 | .build(); 104 | 105 | Sort sort = Sorts.builder() 106 | .asc(jack.getAge() != null, "age") 107 | .asc("name") 108 | .asc(jack.getCompany() != null, "company") 109 | .build(); 110 | 111 | List persons = personRepository.findAll(specification, sort); 112 | 113 | // then 114 | assertThat(persons.get(0)).isEqualToComparingFieldByField(aaron); 115 | assertThat(persons.get(2)).isEqualToComparingFieldByField(eric); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/integration/VirtualViewTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.integration; 23 | 24 | import com.github.wenhao.jpa.Specifications; 25 | import com.github.wenhao.jpa.builder.PersonBuilder; 26 | import com.github.wenhao.jpa.model.Person; 27 | import com.github.wenhao.jpa.model.PersonIdCard; 28 | import com.github.wenhao.jpa.repository.PersonIdCardRepository; 29 | import com.github.wenhao.jpa.repository.PersonRepository; 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | import org.junit.jupiter.api.Test; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 34 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 35 | import org.springframework.data.jpa.domain.Specification; 36 | 37 | import java.util.List; 38 | 39 | @DataJpaTest 40 | public class VirtualViewTest { 41 | @Autowired 42 | private PersonRepository personRepository; 43 | @Autowired 44 | private PersonIdCardRepository personIdCardRepository; 45 | @Autowired 46 | private TestEntityManager entityManager; 47 | 48 | @Test 49 | public void should_be_able_to_query_from_virtual_view() { 50 | // given 51 | Person jack = new PersonBuilder() 52 | .name("Jack") 53 | .age(18) 54 | .idCard("100000000000000000") 55 | .build(); 56 | Person eric = new PersonBuilder() 57 | .name("Eric") 58 | .age(20) 59 | .idCard("200000000000000000") 60 | .build(); 61 | Person jackson = new PersonBuilder() 62 | .age(30) 63 | .nickName("Jackson") 64 | .idCard("300000000000000000") 65 | .build(); 66 | personRepository.save(jack); 67 | personRepository.save(eric); 68 | personRepository.save(jackson); 69 | entityManager.flush(); 70 | 71 | // when 72 | Specification specification = Specifications.and() 73 | .gt("age", 18) 74 | .build(); 75 | List personIdCards = personIdCardRepository.findAll(specification); 76 | 77 | // then 78 | assertThat(personIdCards.size()).isEqualTo(2); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/model/Address.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.model; 23 | 24 | import java.util.HashSet; 25 | import java.util.Set; 26 | 27 | import javax.persistence.Entity; 28 | import javax.persistence.GeneratedValue; 29 | import javax.persistence.Id; 30 | import javax.persistence.ManyToMany; 31 | import javax.persistence.Table; 32 | 33 | @Entity 34 | @Table(name = "address") 35 | public class Address { 36 | @Id 37 | @GeneratedValue 38 | private Long id; 39 | private String street; 40 | private Integer number; 41 | 42 | @ManyToMany(mappedBy = "addresses") 43 | private Set owners = new HashSet(); 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | 53 | public String getStreet() { 54 | return street; 55 | } 56 | 57 | public void setStreet(String street) { 58 | this.street = street; 59 | } 60 | 61 | public Integer getNumber() { 62 | return number; 63 | } 64 | 65 | public void setNumber(Integer number) { 66 | this.number = number; 67 | } 68 | 69 | public Set getOwners() { 70 | return owners; 71 | } 72 | 73 | public void setOwners(Set owners) { 74 | this.owners = owners; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/model/IdCard.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.model; 23 | 24 | import javax.persistence.Entity; 25 | import javax.persistence.GeneratedValue; 26 | import javax.persistence.Id; 27 | import javax.persistence.JoinColumn; 28 | import javax.persistence.OneToOne; 29 | import javax.persistence.Table; 30 | 31 | @Entity 32 | @Table(name = "id_card") 33 | public class IdCard { 34 | @Id 35 | @GeneratedValue 36 | private Long id; 37 | private String number; 38 | @OneToOne 39 | @JoinColumn(name = "person_id") 40 | private Person person; 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public void setId(Long id) { 47 | this.id = id; 48 | } 49 | 50 | public String getNumber() { 51 | return number; 52 | } 53 | 54 | public void setNumber(String number) { 55 | this.number = number; 56 | } 57 | 58 | public Person getPerson() { 59 | return person; 60 | } 61 | 62 | public void setPerson(Person person) { 63 | this.person = person; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/model/Person.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.model; 23 | 24 | import static javax.persistence.CascadeType.ALL; 25 | 26 | import java.util.Date; 27 | import java.util.HashSet; 28 | import java.util.Set; 29 | 30 | import javax.persistence.Column; 31 | import javax.persistence.Entity; 32 | import javax.persistence.FetchType; 33 | import javax.persistence.GeneratedValue; 34 | import javax.persistence.Id; 35 | import javax.persistence.JoinColumn; 36 | import javax.persistence.ManyToMany; 37 | import javax.persistence.OneToMany; 38 | import javax.persistence.OneToOne; 39 | import javax.persistence.Table; 40 | 41 | @Entity 42 | @Table(name = "person") 43 | public class Person { 44 | @Id 45 | @GeneratedValue 46 | private Long id; 47 | private Integer age; 48 | private String name; 49 | @Column(name = "nick_name") 50 | private String nickName; 51 | private String company; 52 | private Date birthday; 53 | private boolean married; 54 | @OneToOne(cascade = ALL) 55 | @JoinColumn(name = "id_card_id") 56 | private IdCard idCard; 57 | @OneToMany(cascade = ALL) 58 | private Set phones = new HashSet(); 59 | @ManyToMany(cascade = ALL, fetch = FetchType.LAZY) 60 | private Set

addresses = new HashSet
(); 61 | 62 | public Long getId() { 63 | return id; 64 | } 65 | 66 | public void setId(Long id) { 67 | this.id = id; 68 | } 69 | 70 | public Integer getAge() { 71 | return age; 72 | } 73 | 74 | public void setAge(Integer age) { 75 | this.age = age; 76 | } 77 | 78 | public String getName() { 79 | return name; 80 | } 81 | 82 | public void setName(String name) { 83 | this.name = name; 84 | } 85 | 86 | public String getNickName() { 87 | return nickName; 88 | } 89 | 90 | public void setNickName(String nickName) { 91 | this.nickName = nickName; 92 | } 93 | 94 | public String getCompany() { 95 | return company; 96 | } 97 | 98 | public void setCompany(String company) { 99 | this.company = company; 100 | } 101 | 102 | public Date getBirthday() { 103 | return birthday; 104 | } 105 | 106 | public void setBirthday(Date birthday) { 107 | this.birthday = birthday; 108 | } 109 | 110 | public boolean isMarried() { 111 | return married; 112 | } 113 | 114 | public void setMarried(final boolean married) { 115 | this.married = married; 116 | } 117 | 118 | public IdCard getIdCard() { 119 | return idCard; 120 | } 121 | 122 | public void setIdCard(IdCard idCard) { 123 | this.idCard = idCard; 124 | } 125 | 126 | public Set getPhones() { 127 | return phones; 128 | } 129 | 130 | public void setPhones(Set phones) { 131 | this.phones = phones; 132 | } 133 | 134 | public Set
getAddresses() { 135 | return addresses; 136 | } 137 | 138 | public void setAddresses(Set
addresses) { 139 | this.addresses = addresses; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/model/PersonIdCard.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.model; 23 | 24 | import org.hibernate.annotations.Immutable; 25 | import org.hibernate.annotations.Subselect; 26 | 27 | import javax.persistence.Entity; 28 | import javax.persistence.Id; 29 | 30 | @Entity 31 | @Immutable 32 | @Subselect("SELECT p.id id, p.name name, p.age age, ic.number number " + 33 | "FROM person p " + 34 | "LEFT JOIN id_card ic " + 35 | "ON p.id_card_id=ic.id") 36 | public class PersonIdCard { 37 | @Id 38 | private Long id; 39 | private String name; 40 | private Integer age; 41 | private String number; 42 | 43 | public Long getId() { 44 | return id; 45 | } 46 | 47 | public void setId(Long id) { 48 | this.id = id; 49 | } 50 | 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | public void setName(String name) { 56 | this.name = name; 57 | } 58 | 59 | public Integer getAge() { 60 | return age; 61 | } 62 | 63 | public void setAge(Integer age) { 64 | this.age = age; 65 | } 66 | 67 | public String getNumber() { 68 | return number; 69 | } 70 | 71 | public void setNumber(String number) { 72 | this.number = number; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/model/Phone.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.model; 23 | 24 | import javax.persistence.Entity; 25 | import javax.persistence.GeneratedValue; 26 | import javax.persistence.Id; 27 | import javax.persistence.ManyToOne; 28 | import javax.persistence.Table; 29 | 30 | @Entity 31 | @Table(name = "phone") 32 | public class Phone { 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | private String number; 38 | private String brand; 39 | @ManyToOne 40 | private Person person; 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public void setId(Long id) { 47 | this.id = id; 48 | } 49 | 50 | public String getNumber() { 51 | return number; 52 | } 53 | 54 | public void setNumber(String number) { 55 | this.number = number; 56 | } 57 | 58 | public String getBrand() { 59 | return brand; 60 | } 61 | 62 | public void setBrand(String brand) { 63 | this.brand = brand; 64 | } 65 | 66 | public Person getPerson() { 67 | return person; 68 | } 69 | 70 | public void setPerson(Person person) { 71 | this.person = person; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/repository/PersonIdCardRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.repository; 23 | 24 | import com.github.wenhao.jpa.model.PersonIdCard; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 27 | 28 | public interface PersonIdCardRepository extends JpaRepository, JpaSpecificationExecutor { 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/repository/PersonRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.repository; 23 | 24 | import com.github.wenhao.jpa.model.Person; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 27 | 28 | public interface PersonRepository extends JpaRepository, JpaSpecificationExecutor { 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/github/wenhao/jpa/repository/PhoneRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019, Wen Hao . 3 | *

4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | *

11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | *

14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.github.wenhao.jpa.repository; 23 | 24 | import com.github.wenhao.jpa.model.Phone; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 27 | 28 | public interface PhoneRepository extends JpaRepository, JpaSpecificationExecutor { 29 | } 30 | -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2019, Wen Hao . 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | spring.datasource.url=jdbc:h2:mem:jpa-spec;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 24 | spring.datasource.driverClassName=org.h2.Driver 25 | spring.datasource.username=sa 26 | spring.datasource.password= 27 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 28 | 29 | spring.jpa.generate-ddl = true 30 | spring.h2.console.enabled = true 31 | spring.h2.console.settings.web-allow-others = true 32 | spring.jpa.hibernate.ddl-auto = none 33 | spring.jpa.show-sql = true 34 | spring.jpa.properties.hibernate.format_sql = true --------------------------------------------------------------------------------