├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── ru │ └── sergkorot │ └── dynamic │ ├── OperationProcessorAutoConfiguration.java │ ├── enums │ └── NestedOperation.java │ ├── operation │ ├── SpecificationGlueOperationProviderImpl.java │ ├── SpecificationOperationProviderImpl.java │ └── SpecificationOperationService.java │ └── util │ └── SpecificationUtils.java └── resources └── META-INF └── spring └── org.springframework.boot.autoconfigure.AutoConfiguration.imports /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: CI-operation-starter 10 | 11 | on: 12 | push: 13 | branches: "*" 14 | pull_request: 15 | branches: [ "main" ] 16 | 17 | 18 | jobs: 19 | build: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up JDK 17 26 | uses: actions/setup-java@v3 27 | with: 28 | java-version: '17' 29 | distribution: 'temurin' 30 | cache: maven 31 | - name: Build with Maven 32 | run: mvn -B package --file pom.xml 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/* 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-operation-starter 2 | Status of Last Deployment:
3 | [![Build Status](https://github.com/serezakorotaev/spring-boot-operation-starter/workflows/CI-operation-starter/badge.svg)](https://github.com/serezakorotaev/spring-boot-operation-starter/actions/workflows/maven.yml)
4 | 5 | ### Library for dynamic search into the database 6 | From maven central
7 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ru.sergkorot/spring-boot-operation-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ru.sergkorot.dynamic/spring-boot-operation-starter) 8 | 9 | ```xml 10 | 11 | ru.sergkorot.dynamic 12 | spring-boot-operation-starter 13 | x.x.x 14 | 15 | ``` 16 | or 17 | ```xml 18 | 19 | 20 | 21 | 22 | ru.sergkorot.dynamic 23 | operation-bom 24 | x.x.x 25 | pom 26 | import 27 | 28 | 29 | 30 | ``` 31 | and after version will pull like into the bom 32 | ```xml 33 | 34 | ru.sergkorot.dynamic 35 | spring-boot-operation-starter 36 | 37 | ``` 38 | 39 | ```gradle 40 | implementation 'ru.sergkorot.dynamic:spring-boot-operation-starter:x.x.x' 41 | ``` 42 | 43 | # Content list 44 | 45 | [About library](#1-about-library) 46 | 47 | 48 | [OperationService](#2-operationservice) 49 | - [SpecificationOperationService](#21-specificationoperationservice) 50 | 51 | [Supported operations](#3-supported-operations) 52 | - [IN](#in) 53 | - [NOT IN](#notin) 54 | - [LIKE](#like) 55 | - [EQUAL](#equal) 56 | - [NOT EQUAL](#notequal) 57 | - [IS NULL](#isnull) 58 | - [LESS THAN](#lessthan) 59 | - [GREATER THAN](#greaterthan) 60 | - [LESS THAN OR EQUALS](#lessthanorequals) 61 | - [GREATER THAN OR EQUALS](#greaterthanorequals) 62 | - [CONTAINS](#contains) 63 | 64 | [Models for searching and paging](#4-models-for-searching-and-paging) 65 | - [BaseSearchParam](#basesearchparam) 66 | - [ComplexSearchParam](#complexsearchparam) 67 | - [PageAttribute](#pageattribute) 68 | 69 | [Request examples](#5-request-examples) 70 | 71 | [Other useful classes](#6-other-useful-classes) 72 | - [Utils](#utils) 73 | - [RegexpUtils](#regexputils) 74 | - [SortUtils](#sortutils) 75 | - [SpecificationUtils](#specificationutils) 76 | - [PageRequestWithOffset](#pagerequestwithoffset) 77 | 78 | ## 1. [About Library](#content-list) 79 | 80 | Library helps to developers to build more flexible requests and page settings. 81 | Using a few library's methods you will have opportunity to build simple and complex requests, 82 | including conjunction and disjunction with different operations such as *equals*, *like*, *lessThan*, 83 | *in* and others. 84 | 85 | 86 | ## 2. [OperationService](#content-list) 87 | 88 | This is an interface for building requests into the databases with different parameters and glue option. 89 | At the moment are existed two implementation of this interface: `SpecificationOperationService` 90 | and `CriteriaOperationService`. `CriteriaOperationService` implementation is used to the [spring-boot-operation-mongodb-starter](https://github.com/serezakorotaev/spring-boot-operation-mongodb-starter) 91 | 92 | ### 2.1 [SpecificationOperationService](#content-list) 93 | 94 | This class is main class in lib for build request into the relation databases. For using you should define the entity 95 | for which will use this class 96 | 97 | `SpecificationOperationService operationService` 98 | 99 | after that, you can build queries for this entity. 100 | 101 | `SpecificationOperationService` implements methods for creating simple and complex requests into the database 102 | and also has method for creating page settings. 103 | 104 | - a. `Specification buildBaseByParams(List baseSearchParams, GlueOperation glue)` 105 | 106 | Method for building base request using search parameters (baseSearchParams) 107 | and condition for linking parameters (glue). `BaseSearchParam` and `GlueOperation` classes will be described 108 | below. 109 | 110 | - b. `Specification buildComplexByParams(List complexSearchParams, GlueOperation externalGlue)` 111 | 112 | Method for building complex request using structure with base search parameters (complexSearchParams) 113 | and condition for linking base parameters with each other (externalGlue). `ComplexSearchParam` class will be described 114 | below. 115 | 116 | - c. `PageRequestWithOffset buildPageSettings(PageAttribute pageAttribute, List searchSortFields)` 117 | 118 | Method for building page settings (limit, offset and sorting) using class with page parameters (pageAttribute) 119 | and list with fields for which will be applied sorting (searchSortFields). `PageAttribute` class will be described 120 | below. 121 | 122 | ## 3. [Supported operations](#content-list) 123 | 124 | In library has different operations for searching. User need to select operation in field "operation" and it will be 125 | processed. 126 | All operations are in `OperationType` and interface for their implementation in `OperationProvider`. 127 | 128 | ### [IN](#content-list) 129 | 130 | IN operation is used for searching records by specified elements 131 | 132 | Example: 133 | { 134 | "name": "name", 135 | "value": "John,Max", 136 | "operation": "in" 137 | } 138 | 139 | In example above, predicate will be built with condition (find all by name in (John,Max)) 140 | 141 | ### [NOT_IN](#content-list) 142 | 143 | NOT_IN operation is used for searching records without specified elements 144 | 145 | Example: 146 | { 147 | "name": "name", 148 | "value": "John,Max", 149 | "operation": "notIn" 150 | } 151 | 152 | In example above, predicate will be built with condition (find all by name not in (John,Max)) 153 | 154 | ### [LIKE](#content-list) 155 | 156 | Like operation is used for searching records where contains specified string 157 | 158 | Example: 159 | { 160 | "name": "name", 161 | "value": "Jo", 162 | "operation": "like" 163 | } 164 | 165 | In example above, predicate will be built with condition (find all by name like (%Jo%)) 166 | 167 | ### [EQUAL](#content-list) 168 | 169 | Equal operation is used for searching records by strict match 170 | 171 | Example: 172 | { 173 | "name": "varsion", 174 | "value": 1, 175 | "operation": "eq" 176 | } 177 | 178 | In example above, predicate will be built with condition (find all where version = 1) 179 | 180 | ### [NOT_EQUAL](#content-list) 181 | 182 | Not equal operation is used for searching records where elements haven't specified value 183 | 184 | Example: 185 | { 186 | "name": "version", 187 | "value": 1, 188 | "operation": "notEq" 189 | } 190 | 191 | In example above, predicate will be built with condition (find all where version != 1) 192 | 193 | ### [IS_NULL](#content-list) 194 | 195 | Is null operation is used for searching records where elements haven't null value 196 | 197 | Example: 198 | { 199 | "name": "version", 200 | "operation": "isNull" 201 | } 202 | 203 | In example above, predicate will be built with condition (find all where version is null) 204 | 205 | ### [LESS_THAN](#content-list) 206 | 207 | Less than operation is used for searching comparing records where elements less than specified 208 | 209 | Example: 210 | { 211 | "name": "age", 212 | "value": 20, 213 | "operation": "lt" 214 | } 215 | 216 | In example above, predicate will be built with condition (find all where age < 20) 217 | 218 | ### [GREATER_THAN](#content-list) 219 | 220 | Greater than operation is used for searching comparing records where elements greater than specified 221 | 222 | Example: 223 | { 224 | "name": "age", 225 | "value": 20, 226 | "operation": "gt" 227 | } 228 | 229 | In example above, predicate will be built with condition (find all where age > 20) 230 | 231 | ### [LESS_THAN_OR_EQUALS](#content-list) 232 | 233 | Less than operation is used for searching comparing records where elements less than or equal specified 234 | 235 | Example: 236 | { 237 | "name": "age", 238 | "value": 21, 239 | "operation": "le" 240 | } 241 | 242 | In example above, predicate will be built with condition (find all where age <= 21) 243 | 244 | ### [GREATER_THAN_OR_EQUALS](#content-list) 245 | 246 | Less than operation is used for searching comparing records where elements greater than or equal specified 247 | 248 | Example: 249 | { 250 | "name": "age", 251 | "value": 21, 252 | "operation": "ge" 253 | } 254 | 255 | In example above, predicate will be built with condition (find all where age >= 21) 256 | 257 | ### [CONTAINS](#content-list) 258 | 259 | Contains operation is used for searching records where elements contains specified values (operation also is used for 260 | jsonb fields) 261 | 262 | Example: 263 | { 264 | "name": "description", 265 | "value": "a,is", 266 | "operation": "contains" 267 | } 268 | 269 | In example above, predicate will be built with condition (find all where description contains (a and is strings)) 270 | 271 | ## 4. [Models for searching and paging](#content-list) 272 | 273 | For searching and paging are three base models - `BaseSearchParam`, `ComplexSearchParam` and `PageAttribute`. 274 | And Also shell for them are `CommonOperationShell` and `MultipleOperationShell`. 275 | 276 | ### [`BaseSearchParam`](#content-list) 277 | 278 | ``` 279 | class BaseSearchParam { 280 | private String name; 281 | private Object value; 282 | private String operation; 283 | } 284 | ``` 285 | 286 | Is base class for searching. it has 287 | 288 | - `name` - field's name which need to search 289 | - `value` - value which need to find 290 | - `operation` - operation name which describe above 291 | 292 | ``` 293 | Example: 294 | { 295 | "name": "name", 296 | "value": "Ian.Hessel", 297 | "operation": "eq" 298 | } 299 | ``` 300 | 301 | ### [`ComplexSearchParam`](#content-list) 302 | 303 | ``` 304 | class ComplexSearchParam { 305 | List baseSearchParams; 306 | GlueOperation internalGlue = AND; 307 | } 308 | ``` 309 | 310 | is more complex class for searching. It has 311 | 312 | - list `baseSearchParams` which has fields and operations for searching 313 | - `internalGlue` which allows you to glue all given conditions. AND/OR value 314 | 315 | ``` 316 | Example: 317 | { 318 | "baseSearchParams": [ 319 | { 320 | "name": "name", 321 | "value": "Ian.Hessel", 322 | "operation": "eq" 323 | }, 324 | { 325 | "name": "description", 326 | "value": "withdrawal", 327 | "operation": "eq" 328 | } 329 | ], 330 | "glue" : "OR" 331 | } 332 | ``` 333 | 334 | ### [`PageAttribute`](#content-list) 335 | 336 | ``` 337 | class PageAttribute { 338 | private Integer limit; 339 | private Integer offset; 340 | private String sortBy; 341 | } 342 | ``` 343 | 344 | Is used for building page settings for paging 345 | 346 | - `limit` Number of list items to return 347 | - `offset` Shift relative to the beginning of the list 348 | - `sortBy` Parameter for sorting. Perhaps multiple sorting through comma (name,-surname), 349 | which means name ASC and surname DESC 350 | 351 | ## 5. [Request examples](#content-list) 352 | 353 | base search: 354 | 355 | ``` 356 | { 357 | "baseSearchParams": [ 358 | { 359 | "name": "name", 360 | "value": "Ian.Hessel", 361 | "operation": "eq" 362 | }, 363 | { 364 | "name": "description", 365 | "value": "withdrawal", 366 | "operation": "eq" 367 | } 368 | ], 369 | "glue" : "OR", 370 | "pageAttribute" : { 371 | "limit" : 10, 372 | "offset" : 0, 373 | "sortBy" : "name" 374 | } 375 | } 376 | ``` 377 | 378 | In example above, is used `CommonOperationShell` for simple request 379 | 380 | ``` 381 | ( 382 | Find all where name.equals("Ian.Hessel") or description.equals("withdrawal") 383 | with limit=10 and offset=0 and sort by name ASC 384 | ) 385 | ``` 386 | 387 | complex search: 388 | 389 | ``` 390 | { 391 | "search": [ 392 | { 393 | "baseSearchParams": [ 394 | { 395 | "name": "name", 396 | "value": "Rhett14", 397 | "operation": "eq" 398 | }, 399 | { 400 | "name": "version", 401 | "value": "0,2", 402 | "operation": "in" 403 | } 404 | ], 405 | "internalGlue": "AND" 406 | }, 407 | { 408 | "baseSearchParams": [ 409 | { 410 | "name": "name", 411 | "value": "Reggie19", 412 | "operation": "eq" 413 | }, 414 | { 415 | "name": "description", 416 | "value": "Up-sized", 417 | "operation": "like" 418 | } 419 | ], 420 | "internalGlue": "AND" 421 | } 422 | ], 423 | "externalGlue": "OR", 424 | "pageAttribute": { 425 | "limit": 10 426 | } 427 | } 428 | ``` 429 | 430 | In example above, is used `MultipleOperationShell` for complex request 431 | 432 | ``` 433 | ( 434 | find all where (name.equals("Rhett14") and version in(0, 2)) or (name.equals("Reggie19") and description like ("%Up-sized%")) 435 | ) 436 | ``` 437 | 438 | ## 6. [Other useful classes](#content-list) 439 | 440 | ### [Utils](#content-list) 441 | 442 | #### [`RegexpUtils`](#content-list) 443 | - `RegexpUtils.transformToArrayFieldsNames(String fieldsNames)` 444 | 445 | Util is used for transforming string by pattern to list strings with strings for further paging 446 | 447 | ``` 448 | Example: 449 | name,-surname,version -> ["name", "-surname", "version] 450 | 451 | ``` 452 | 453 | #### [`SortUtils`](#content-list) 454 | 455 | - `SortUtils.makeSortOrders(final Collection validNames, final String sortValues)` 456 | 457 | Util is used for transforming string by pattern inside (uses RegexpUtils) to list 458 | org.springframework.data.domain.Sort.Order class 459 | and checking by validNames if it can build Sort.Order by these sortValue names 460 | 461 | - `SortUtils.makeSort(final Collection validNames, final String sortValues)` 462 | 463 | Util is used for transforming string by pattern inside (uses RegexpUtils) to list 464 | org.springframework.data.domain.Sort class 465 | and checking by validNames if it can build Sort.Order by these sortValue names 466 | 467 | ``` 468 | Example: 469 | validNames : name,-surname 470 | sortValues: name,-surname,version -> Not found parameter with name: version 471 | 472 | ``` 473 | 474 | #### [`SpecificationUtils`](#content-list) 475 | 476 | Util is used for building different specifications for request. Contains a lot of static methods: 477 | 478 | - `findByColumnEquals(Object value, String columnName)` 479 | - `findByColumnNotEquals(Object value, String columnName)` 480 | - `findByColumnsLike(String value, Collection columnName)` 481 | - `lessThan(Y value, String columnName)` 482 | - `greaterThan(Y value, String columnName)` 483 | - `lessThanOrEqual(Y value, String columnName)` 484 | - `greaterThanOrEqual(Y value, String columnName)` 485 | - `findByCollectionIn(Collection collection, String columnName)` 486 | - `findByCollectionNotIn(Collection collection, String columnName)` 487 | - `findByColumnIsNull(String columnName)` 488 | - `findNothing()` 489 | - `findAll()` 490 | - `contains(Object value, String columnName)` 491 | 492 | More detail in javadoc 493 | 494 | #### [`PageRequestWithOffset`](#content-list) 495 | 496 | PageRequest extension for building page settings 497 | 498 | ``` 499 | Example: 500 | PageRequest request = PageRequestWithOffset.of(offset: 0, size: 10, List: List.of(Order.by(name).asc())) 501 | 502 | ``` 503 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.1.4 11 | 12 | 13 | 14 | ru.sergkorot.dynamic 15 | spring-boot-operation-starter 16 | 3.3.1 17 | 18 | Dynamic search 19 | Library for dynamic searching into the databases 20 | https://github.com/serezakorotaev/spring-boot-operation-starter 21 | 22 | 23 | 24 | Sergey Korotaev 25 | sergey.evgen.kor2501@gmail.com 26 | https://github.com/serezakorotaev 27 | 28 | 29 | 30 | 31 | 32 | Apache License 33 | http://www.apache.org/licenses/ 34 | 35 | 36 | 37 | 38 | scm:git:https://github.com/serezakorotaev/spring-boot-operation-starter.git 39 | https://github.com/serezakorotaev/spring-boot-operation-starter 40 | scm:git:https://github.com/serezakorotaev/spring-boot-operation-starter.git 41 | 42 | 43 | 44 | 45 | ossrh 46 | https://s01.oss.sonatype.org/ 47 | 48 | 49 | 50 | 51 | 17 52 | 17 53 | UTF-8 54 | 1.6.13 55 | 3.2.1 56 | 3.5.0 57 | 3.0.1 58 | 59 | 60 | 61 | 62 | ru.sergkorot.dynamic 63 | operation-core 64 | 1.1.1 65 | 66 | 67 | org.yaml 68 | snakeyaml 69 | 2.2 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-data-jpa 74 | 75 | 76 | jakarta.persistence 77 | jakarta.persistence-api 78 | 79 | 80 | org.projectlombok 81 | lombok 82 | true 83 | 84 | 85 | org.junit.jupiter 86 | junit-jupiter-engine 87 | test 88 | 89 | 90 | com.fasterxml.jackson.dataformat 91 | jackson-dataformat-xml 92 | 2.16.0 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-compiler-plugin 101 | 102 | 103 | 104 | org.projectlombok 105 | lombok 106 | ${lombok.version} 107 | 108 | 109 | 17 110 | 17 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.sonatype.plugins 118 | nexus-staging-maven-plugin 119 | ${plugin.nexus.staging.ver} 120 | true 121 | 122 | ossrh 123 | https://s01.oss.sonatype.org/ 124 | true 125 | 126 | 127 | 128 | org.apache.maven.plugins 129 | maven-source-plugin 130 | ${plugin.maven.source.ver} 131 | 132 | 133 | attach-sources 134 | 135 | jar-no-fork 136 | 137 | 138 | 139 | 140 | 141 | org.apache.maven.plugins 142 | maven-javadoc-plugin 143 | ${plugin.maven.javadoc.ver} 144 | 145 | 146 | attach-javadocs 147 | 148 | jar 149 | 150 | 151 | 152 | 153 | 154 | org.apache.maven.plugins 155 | maven-gpg-plugin 156 | ${plugin.maven.gpg.ver} 157 | 158 | 159 | sign-artifacts 160 | verify 161 | 162 | sign 163 | 164 | 165 | 166 | 167 | 168 | --pinentry-mode 169 | loopback 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | release 180 | 181 | 182 | 183 | org.sonatype.plugins 184 | nexus-staging-maven-plugin 185 | 186 | 187 | org.apache.maven.plugins 188 | maven-source-plugin 189 | 190 | 191 | org.apache.maven.plugins 192 | maven-gpg-plugin 193 | 194 | 195 | org.apache.maven.plugins 196 | maven-javadoc-plugin 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/OperationProcessorAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | 5 | /** 6 | * Main library class 7 | */ 8 | @ComponentScan 9 | public class OperationProcessorAutoConfiguration { 10 | } -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/enums/NestedOperation.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic.enums; 2 | 3 | import jakarta.persistence.criteria.Subquery; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import org.springframework.data.jpa.domain.Specification; 7 | import ru.sergkorot.dynamic.util.SpecificationUtils; 8 | 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.stream.Stream; 12 | 13 | import static java.util.stream.Collectors.toUnmodifiableMap; 14 | 15 | /** 16 | * Enum for construction nested query by operation name 17 | * and field name for returning 18 | */ 19 | @Getter 20 | @AllArgsConstructor 21 | public enum NestedOperation { 22 | 23 | /** 24 | * Element of enum for constructing in operation 25 | */ 26 | IN("in") { 27 | @Override 28 | public Specification buildQuery(String fieldName, Subquery subquery) { 29 | return (root, query, criteriaBuilder) -> criteriaBuilder.in(root.get(fieldName)).value(subquery); 30 | } 31 | }, 32 | 33 | /** 34 | * Element of enum for constructing not in operation 35 | */ 36 | NOT_IN("notIn") { 37 | @Override 38 | public Specification buildQuery(String fieldName, Subquery subquery) { 39 | return (root, query, criteriaBuilder) -> criteriaBuilder.not(criteriaBuilder.in(root.get(fieldName)).value(subquery)); 40 | } 41 | }, 42 | 43 | /** 44 | * Element of enum for constructing equals operation 45 | */ 46 | EQUAL("eq") { 47 | @Override 48 | public Specification buildQuery(String fieldName, Subquery subquery) { 49 | return SpecificationUtils.findByColumnEquals(subquery, fieldName); 50 | } 51 | }, 52 | 53 | /** 54 | * Element of enum for constructing not equals operation 55 | */ 56 | NOT_EQUAL("notEq") { 57 | @Override 58 | public Specification buildQuery(String fieldName, Subquery subquery) { 59 | return SpecificationUtils.findByColumnNotEquals(subquery, fieldName); 60 | } 61 | }, 62 | 63 | /** 64 | * Element of enum for constructing is null operation 65 | */ 66 | IS_NULL("isNull") { 67 | @Override 68 | public Specification buildQuery(String fieldName, Subquery subquery) { 69 | return SpecificationUtils.findByColumnIsNull(fieldName); 70 | } 71 | }, 72 | 73 | /** 74 | * Element of enum for constructing less than operation 75 | */ 76 | LESS_THAN("lt") { 77 | @Override 78 | public Specification buildQuery(String fieldName, Subquery subquery) { 79 | return SpecificationUtils.lessThan(subquery.getSelection().as(String.class), fieldName); 80 | } 81 | }, 82 | 83 | /** 84 | * Element of enum for constructing greater than operation 85 | */ 86 | GREATER_THAN("gt") { 87 | @Override 88 | public Specification buildQuery(String fieldName, Subquery subquery) { 89 | return SpecificationUtils.greaterThan(subquery.getSelection().as(String.class), fieldName); 90 | } 91 | }, 92 | 93 | /** 94 | * Element of enum for constructing less than or equals operation 95 | */ 96 | LESS_THAN_OR_EQUALS("le") { 97 | @Override 98 | public Specification buildQuery(String fieldName, Subquery subquery) { 99 | return SpecificationUtils.lessThanOrEqual(subquery.getSelection().as(String.class), fieldName); 100 | } 101 | }, 102 | 103 | /** 104 | * Element of enum for constructing greater than or equals operation 105 | */ 106 | GREATER_THAN_OR_EQUALS("ge") { 107 | @Override 108 | public Specification buildQuery(String fieldName, Subquery subquery) { 109 | return SpecificationUtils.greaterThanOrEqual(subquery.getSelection().as(String.class), fieldName); 110 | } 111 | }, 112 | 113 | /** 114 | * Element of enum for constructing contains operation 115 | */ 116 | CONTAINS("contains") { 117 | @Override 118 | public Specification buildQuery(String fieldName, Subquery subquery) { 119 | return SpecificationUtils.contains(subquery.getSelection(), fieldName); 120 | } 121 | }; 122 | 123 | 124 | private final String operationName; 125 | 126 | private static final Map operationMap = Stream.of(values()) 127 | .collect(toUnmodifiableMap( 128 | NestedOperation::getOperationName, 129 | type -> type)); 130 | 131 | /** 132 | * Get NestedOperation by operation name 133 | * 134 | * @param typeName - operation name 135 | * @return NestedOperation 136 | */ 137 | public static NestedOperation of(String typeName) { 138 | return Optional.ofNullable(operationMap.get(typeName)) 139 | .orElseThrow(() -> new IllegalArgumentException(String.format("operation %s not supported", typeName))); 140 | } 141 | 142 | /** 143 | * Build specification for nested query by returning field and subquery 144 | * 145 | * @param fieldName - field for returning in nested query 146 | * @param subquery - sub query in request 147 | * @param - type of entity 148 | * @return Specification for field 149 | */ 150 | public abstract Specification buildQuery(String fieldName, 151 | Subquery subquery); 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/operation/SpecificationGlueOperationProviderImpl.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic.operation; 2 | 3 | import org.springframework.data.jpa.domain.Specification; 4 | import org.springframework.stereotype.Component; 5 | import ru.sergkorot.dynamic.glue.Glue; 6 | import ru.sergkorot.dynamic.glue.GlueOperationProvider; 7 | 8 | @Component 9 | public class SpecificationGlueOperationProviderImpl implements GlueOperationProvider> { 10 | 11 | @Override 12 | public Glue> and() { 13 | return Specification::allOf; 14 | } 15 | 16 | @Override 17 | public Glue> or() { 18 | return Specification::anyOf; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/operation/SpecificationOperationProviderImpl.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic.operation; 2 | 3 | import org.springframework.data.jpa.domain.Specification; 4 | import org.springframework.stereotype.Component; 5 | import ru.sergkorot.dynamic.util.SpecificationUtils; 6 | 7 | import java.util.Collections; 8 | 9 | import static org.springframework.data.jpa.domain.Specification.where; 10 | import static ru.sergkorot.dynamic.model.enums.ValueType.cast; 11 | import static ru.sergkorot.dynamic.model.enums.ValueType.collectionCast; 12 | 13 | /** 14 | * @author Sergey Korotaev 15 | * Service is realization Operation provider interface for building different specifications 16 | * @param - the entity for which the request is being built 17 | * @see OperationProvider 18 | */ 19 | @Component 20 | public class SpecificationOperationProviderImpl implements OperationProvider> { 21 | 22 | @Override 23 | public Operation> like() { 24 | return param -> where(SpecificationUtils.findByColumnsLike(param.getValue().toString(), Collections.singleton(param.getName()))); 25 | } 26 | 27 | @Override 28 | public Operation> eq() { 29 | return param -> where(SpecificationUtils.findByColumnEquals(cast(param.getValue()), param.getName())); 30 | } 31 | 32 | @Override 33 | public Operation> notEq() { 34 | return param -> where(SpecificationUtils.findByColumnNotEquals(cast(param.getValue()), param.getName())); 35 | } 36 | 37 | @Override 38 | public Operation> in() { 39 | return param -> 40 | where(SpecificationUtils.findByCollectionIn(collectionCast(param.getValue()), param.getName())); 41 | } 42 | 43 | @Override 44 | public Operation> notIn() { 45 | return param -> 46 | where(SpecificationUtils.findByCollectionNotIn(collectionCast(param.getValue()), param.getName())); 47 | } 48 | 49 | @Override 50 | public Operation> isNull() { 51 | return param -> where(SpecificationUtils.findByColumnIsNull(param.getName())); 52 | } 53 | 54 | @Override 55 | public Operation> lessThan() { 56 | return param -> where(SpecificationUtils.lessThan(param.getValue().toString(), param.getName())); 57 | } 58 | 59 | @Override 60 | public Operation> greaterThan() { 61 | return param -> where(SpecificationUtils.greaterThan(param.getValue().toString(), param.getName())); 62 | } 63 | 64 | @Override 65 | public Operation> greaterThanOrEquals() { 66 | return param -> where(SpecificationUtils.greaterThanOrEqual(param.getValue().toString(), param.getName())); 67 | } 68 | 69 | @Override 70 | public Operation> lessThanOrEquals() { 71 | return param -> where(SpecificationUtils.lessThanOrEqual(param.getValue().toString(), param.getName())); 72 | } 73 | 74 | public Operation> contains() { 75 | return param -> where(SpecificationUtils.contains(param.getValue(), param.getName())); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/operation/SpecificationOperationService.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic.operation; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.persistence.criteria.Predicate; 5 | import jakarta.persistence.criteria.Root; 6 | import jakarta.persistence.criteria.Subquery; 7 | import org.springframework.data.jpa.domain.Specification; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.util.CollectionUtils; 10 | import ru.sergkorot.dynamic.enums.NestedOperation; 11 | import ru.sergkorot.dynamic.glue.GlueOperationProvider; 12 | import ru.sergkorot.dynamic.model.BaseSearchParam; 13 | import ru.sergkorot.dynamic.model.ComplexSearchParam; 14 | import ru.sergkorot.dynamic.model.PageAttribute; 15 | import ru.sergkorot.dynamic.model.enums.GlueOperation; 16 | import ru.sergkorot.dynamic.model.paging.PageRequestWithOffset; 17 | import ru.sergkorot.dynamic.util.SortUtils; 18 | import ru.sergkorot.dynamic.util.SpecificationUtils; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Objects; 23 | import java.util.function.Function; 24 | import java.util.stream.Collectors; 25 | 26 | /** 27 | * @param - entity for which building condition 28 | * @author Sergey Korotaev 29 | * Service for building specification for base and complex requests. 30 | * Also for building page request settings 31 | */ 32 | @Service 33 | @SuppressWarnings("unused") 34 | public class SpecificationOperationService implements OperationService> { 35 | 36 | private static final String NESTED = "nst:"; 37 | private final OperationProvider> operationProvider; 38 | private final GlueOperationProvider> glueOperationProvider; 39 | private final Map>> manualOperationProviderMap; 40 | private final ObjectMapper objectMapper; 41 | 42 | 43 | public SpecificationOperationService(OperationProvider> operationProvider, 44 | GlueOperationProvider> glueOperationProvider, 45 | List>> manualOperationProviders, 46 | ObjectMapper objectMapper) { 47 | this.operationProvider = operationProvider; 48 | this.glueOperationProvider = glueOperationProvider; 49 | this.manualOperationProviderMap = CollectionUtils.isEmpty(manualOperationProviders) 50 | ? null 51 | : manualOperationProviders.stream().collect(Collectors.toMap(ManualOperationProvider::fieldName, Function.identity())); 52 | this.objectMapper = objectMapper; 53 | } 54 | 55 | /** 56 | * Create specification for base search request 57 | * 58 | * @param baseSearchParams - model for base search request 59 | * @param glue - condition for gluing specifications 60 | * @return - specification for data request 61 | * @see BaseSearchParam 62 | * @see GlueOperation 63 | */ 64 | public Specification buildBaseByParams(List baseSearchParams, GlueOperation glue) { 65 | if (CollectionUtils.isEmpty(baseSearchParams)) { 66 | return SpecificationUtils.findAll(); 67 | } 68 | 69 | List> specifications = baseSearchParams 70 | .stream() 71 | .map(this::constructSpecification) 72 | .toList(); 73 | 74 | return buildGlue(glueOperationProvider, specifications, glue); 75 | } 76 | 77 | /** 78 | * Create specification for complex search request 79 | * 80 | * @param complexSearchParams - model for complex search request 81 | * @param externalGlue - condition for gluing complex specification between each other 82 | * @return - specification for data request 83 | * @see ComplexSearchParam 84 | * @see GlueOperation 85 | */ 86 | public Specification buildComplexByParams(List complexSearchParams, GlueOperation externalGlue) { 87 | 88 | List> specifications = complexSearchParams.stream() 89 | .map(complexSearchParam -> 90 | buildBaseByParams( 91 | complexSearchParam.getBaseSearchParams(), 92 | complexSearchParam.getInternalGlue() 93 | ) 94 | ) 95 | .toList(); 96 | 97 | return buildGlue(glueOperationProvider, specifications, externalGlue); 98 | } 99 | 100 | /** 101 | * Create PageRequest extension for paging and sorting settings 102 | * 103 | * @param pageAttribute - attribute class for pagination and sorting 104 | * @param searchSortFields - fields by which sorting is possible in the database 105 | * @return - PageRequestWithOffset 106 | * @see PageRequestWithOffset 107 | * @see PageAttribute 108 | */ 109 | public PageRequestWithOffset buildPageSettings(PageAttribute pageAttribute, List searchSortFields) { 110 | if (Objects.isNull(pageAttribute)) { 111 | return PageRequestWithOffset.of(SortUtils.DEFAULT_OFFSET, SortUtils.DEFAULT_LIMIT, List.of()); 112 | } 113 | return PageRequestWithOffset.of( 114 | pageAttribute.getOffset(), 115 | pageAttribute.getLimit(), 116 | SortUtils.makeSortOrders(searchSortFields, pageAttribute.getSortBy()) 117 | ); 118 | } 119 | 120 | private Specification constructSpecification(BaseSearchParam param) { 121 | if (!CollectionUtils.isEmpty(manualOperationProviderMap) && manualOperationProviderMap.containsKey(param.getName())) { 122 | return manualOperationProviderMap.get(param.getName()).buildOperation(param); 123 | } 124 | 125 | if (param.getOperation().startsWith(NESTED)) { 126 | param.setOperation(param.getOperation().replace(NESTED, "")); 127 | return buildNestedOperation(param); 128 | } 129 | 130 | return buildOperation(param, operationProvider); 131 | } 132 | 133 | private Specification buildNestedOperation(BaseSearchParam param) { 134 | return (root, query, criteriaBuilder) -> { 135 | 136 | Subquery subquery = query.subquery(Object.class); 137 | Root subroot = subquery.from(root.getModel()); 138 | 139 | subquery.select(subroot.get(param.getName())); 140 | 141 | ComplexSearchParam nestedParam = objectMapper.convertValue(param.getValue(), ComplexSearchParam.class); 142 | 143 | Predicate predicate = buildBaseByParams(nestedParam.getBaseSearchParams(), nestedParam.getInternalGlue()) 144 | .toPredicate(subroot, query, criteriaBuilder); 145 | 146 | subquery.where(predicate); 147 | 148 | return NestedOperation.of(param.getOperation()) 149 | .buildQuery( 150 | param.getName(), 151 | subquery 152 | ) 153 | .toPredicate(root, query, criteriaBuilder); 154 | }; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/ru/sergkorot/dynamic/util/SpecificationUtils.java: -------------------------------------------------------------------------------- 1 | package ru.sergkorot.dynamic.util; 2 | 3 | import jakarta.persistence.criteria.CriteriaBuilder; 4 | import jakarta.persistence.criteria.Expression; 5 | import jakarta.persistence.criteria.Predicate; 6 | import lombok.AccessLevel; 7 | import lombok.NoArgsConstructor; 8 | import org.apache.commons.lang3.function.TriFunction; 9 | import org.springframework.data.jpa.domain.Specification; 10 | import org.springframework.lang.NonNull; 11 | import org.springframework.util.CollectionUtils; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.function.Function; 16 | 17 | /** 18 | * @author Sergey Korotaev 19 | * Util is used for building different specifications for request 20 | */ 21 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 22 | public final class SpecificationUtils { 23 | 24 | /** 25 | * Find column equals specified 26 | * 27 | * @param value - the value to which the entry in the database should be equivalent 28 | * @param columnName - name of column into the database 29 | * @param - the entity for which the request is being built 30 | * @return Specification 31 | */ 32 | @NonNull 33 | public static Specification findByColumnEquals(Object value, @NonNull String columnName) { 34 | return (root, criteriaQuery, criteriaBuilder) -> 35 | criteriaBuilder.equal(root.get(columnName), value); 36 | } 37 | 38 | /** 39 | * Find column not equals specified 40 | * 41 | * @param value - the value to which the entry in the database should not be equivalent 42 | * @param columnName - name of column into the database 43 | * @param - the entity for which the request is being built 44 | * @return Specification 45 | */ 46 | @NonNull 47 | public static Specification findByColumnNotEquals(Object value, @NonNull String columnName) { 48 | return (root, criteriaQuery, criteriaBuilder) -> 49 | criteriaBuilder.notEqual(root.get(columnName), value); 50 | } 51 | 52 | /** 53 | * Find column like specified 54 | * 55 | * @param value - the value to which the entry in the database should be similar 56 | * @param columnName - name of columns into the database 57 | * @param - the entity for which the request is being built 58 | * @return Specification 59 | */ 60 | @NonNull 61 | public static Specification findByColumnsLike(@NonNull String value, @NonNull Collection columnName) { 62 | if (!value.contains("%")) { 63 | value = "%" + value + "%"; 64 | } 65 | String textForSearch = value.toLowerCase(); 66 | return (root, criteriaQuery, criteriaBuilder) -> 67 | criteriaBuilder.or(columnName.stream() 68 | .map(element -> criteriaBuilder.like(criteriaBuilder.lower(root.get(element)), textForSearch)) 69 | .toArray(Predicate[]::new)); 70 | } 71 | 72 | /** 73 | * Find column less than specified value 74 | * 75 | * @param value - the value to which the entry in the database should be less 76 | * @param columnName - name of column into the database 77 | * @param - the entity for which the request is being built 78 | * @param - comparable value 79 | * @return Specification 80 | */ 81 | @NonNull 82 | public static > Specification lessThan(Y value, @NonNull String columnName) { 83 | if (value == null) { 84 | return Specification.where(null); 85 | } 86 | return (root, query, criteriaBuilder) -> criteriaBuilder.lessThan(root.get(columnName), value); 87 | } 88 | 89 | /** 90 | * Find column less than specified value 91 | * 92 | * @param value - the value to which the entry in the database should be less 93 | * @param columnName - name of column into the database 94 | * @param - the entity for which the request is being built 95 | * @param - comparable value 96 | * @return Specification 97 | */ 98 | @NonNull 99 | public static > Specification lessThan(Expression value, @NonNull String columnName) { 100 | if (value == null) { 101 | return Specification.where(null); 102 | } 103 | return (root, query, criteriaBuilder) -> criteriaBuilder.lessThan(root.get(columnName), value); 104 | } 105 | 106 | /** 107 | * Find column greater than specified value 108 | * 109 | * @param value - the value to which the entry in the database should be greater 110 | * @param columnName - name of column into the database 111 | * @param - the entity for which the request is being built 112 | * @param - comparable value 113 | * @return Specification 114 | */ 115 | @NonNull 116 | public static > Specification greaterThan(Y value, @NonNull String columnName) { 117 | if (value == null) { 118 | return Specification.where(null); 119 | } 120 | return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get(columnName), value); 121 | } 122 | 123 | /** 124 | * Find column greater than specified value 125 | * 126 | * @param value - the value to which the entry in the database should be greater 127 | * @param columnName - name of column into the database 128 | * @param - the entity for which the request is being built 129 | * @param - comparable value 130 | * @return Specification 131 | */ 132 | @NonNull 133 | public static > Specification greaterThan(Expression value, @NonNull String columnName) { 134 | if (value == null) { 135 | return Specification.where(null); 136 | } 137 | return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get(columnName), value); 138 | } 139 | 140 | /** 141 | * Find column less than or equals specified value 142 | * 143 | * @param value - the value to which the entry in the database should be less or equals 144 | * @param columnName - name of column into the database 145 | * @param - the entity for which the request is being built 146 | * @param - comparable value 147 | * @return Specification 148 | */ 149 | @NonNull 150 | public static > Specification lessThanOrEqual(Y value, @NonNull String columnName) { 151 | if (value == null) { 152 | return Specification.where(null); 153 | } 154 | return (root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get(columnName), value); 155 | } 156 | 157 | /** 158 | * Find column less than or equals specified value 159 | * 160 | * @param value - the value to which the entry in the database should be less or equals 161 | * @param columnName - name of column into the database 162 | * @param - the entity for which the request is being built 163 | * @param - comparable value 164 | * @return Specification 165 | */ 166 | @NonNull 167 | public static > Specification lessThanOrEqual(Expression value, @NonNull String columnName) { 168 | if (value == null) { 169 | return Specification.where(null); 170 | } 171 | return (root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get(columnName), value); 172 | } 173 | 174 | /** 175 | * Find column greater than or equals specified value 176 | * 177 | * @param value - the value to which the entry in the database should be greater or equals 178 | * @param columnName - name of column into the database 179 | * @param - the entity for which the request is being built 180 | * @param - comparable value 181 | * @return Specification 182 | */ 183 | @NonNull 184 | public static > Specification greaterThanOrEqual(Y value, @NonNull String columnName) { 185 | if (value == null) { 186 | return Specification.where(null); 187 | } 188 | return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get(columnName), value); 189 | } 190 | 191 | /** 192 | * Find column greater than or equals specified value 193 | * 194 | * @param value - the value to which the entry in the database should be greater or equals 195 | * @param columnName - name of column into the database 196 | * @param - the entity for which the request is being built 197 | * @param - comparable value 198 | * @return Specification 199 | */ 200 | @NonNull 201 | public static > Specification greaterThanOrEqual(Expression value, @NonNull String columnName) { 202 | if (value == null) { 203 | return Specification.where(null); 204 | } 205 | return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get(columnName), value); 206 | } 207 | 208 | /** 209 | * Find entry where value into the column contains in specified collection 210 | * 211 | * @param collection - collection values for searching 212 | * @param columnName - name of column into the database 213 | * @param - the entity for which the request is being built 214 | * @return Specification 215 | */ 216 | @NonNull 217 | public static Specification findByCollectionIn(@NonNull Collection collection, @NonNull String columnName) { 218 | Specification specification; 219 | if (CollectionUtils.isEmpty(collection)) { 220 | specification = findNothing(); 221 | } else { 222 | specification = (root, criteriaQuery, criteriaBuilder) -> 223 | root.get(columnName).in(collection); 224 | } 225 | return specification; 226 | } 227 | 228 | /** 229 | * Find entry where value into the column not contains in specified collection 230 | * 231 | * @param collection - collection values for searching 232 | * @param columnName - name of column into the database 233 | * @param - the entity for which the request is being built 234 | * @return Specification 235 | */ 236 | @NonNull 237 | public static Specification findByCollectionNotIn(@NonNull Collection collection, @NonNull String columnName) { 238 | Specification specification; 239 | if (CollectionUtils.isEmpty(collection)) { 240 | specification = findNothing(); 241 | } else { 242 | specification = (root, criteriaQuery, criteriaBuilder) -> 243 | criteriaBuilder.not(root.get(columnName).in(collection)); 244 | } 245 | return specification; 246 | } 247 | 248 | /** 249 | * Find entry where specified column is null 250 | * 251 | * @param columnName - name of column into the database 252 | * @param - the entity for which the request is being built 253 | * @return Specification 254 | */ 255 | @NonNull 256 | public static Specification findByColumnIsNull(@NonNull String columnName) { 257 | return (root, query, criteriaBuilder) -> 258 | criteriaBuilder.isNull(root.get(columnName)); 259 | } 260 | 261 | /** 262 | * Find nothing into the database 263 | * 264 | * @param - the entity for which the request is being built 265 | * @return Specification 266 | */ 267 | @NonNull 268 | public static Specification findNothing() { 269 | return (root, criteriaQuery, criteriaBuilder) -> 270 | criteriaBuilder.conjunction().not(); 271 | } 272 | 273 | /** 274 | * Find all entry into the database 275 | * 276 | * @param - the entity for which the request is being built 277 | * @return Specification 278 | */ 279 | @NonNull 280 | public static Specification findAll() { 281 | return (root, query, criteriaBuilder) -> 282 | query.where(criteriaBuilder.conjunction()).getRestriction(); 283 | } 284 | 285 | /** 286 | * Find entry which value contains all specified elements 287 | * 288 | * @param value - single or list elements with comma separator 289 | * @param columnName - name of column into the database 290 | * @param - the entity for which the request is being built 291 | * @return Specification 292 | */ 293 | @NonNull 294 | public static Specification contains(@NonNull Object value, @NonNull String columnName) { 295 | return (root, query, criteriaBuilder) -> query.where(criteriaBuilder.and( 296 | Arrays.stream(value.toString().split(",")) 297 | .map(v -> 298 | createPredicate.apply( 299 | trimAndGlue.apply(v), 300 | root.get(columnName), 301 | criteriaBuilder) 302 | ) 303 | .toArray(Predicate[]::new) 304 | )).getRestriction(); 305 | 306 | } 307 | 308 | private static final Function trimAndGlue = v -> "%" + v.trim() + "%"; 309 | private static final TriFunction, CriteriaBuilder, Predicate> createPredicate = 310 | (v, expression, cb) -> cb.like(expression.as(String.class), v); 311 | } 312 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | ru.sergkorot.dynamic.OperationProcessorAutoConfiguration --------------------------------------------------------------------------------