├── .gitignore
├── LICENSE.MD
├── README.md
├── images
├── crud.png
└── query.png
├── pom.xml
└── src
└── main
├── java
└── com
│ └── htech
│ ├── data
│ └── jpa
│ │ └── reactive
│ │ ├── config
│ │ ├── EnableReactiveJpaAuditing.java
│ │ └── ReactiveJpaAuditingRegistrar.java
│ │ ├── core
│ │ ├── ReactiveJpaDataAutoConfiguration.java
│ │ ├── StageReactiveJpaEntityOperations.java
│ │ └── StageReactiveJpaEntityTemplate.java
│ │ ├── mapping
│ │ └── event
│ │ │ ├── BeforeSaveCallback.java
│ │ │ └── ReactiveAuditingEntityCallback.java
│ │ └── repository
│ │ ├── ReactiveCrudRepository.java
│ │ ├── ReactiveJpaRepository.java
│ │ ├── ReactiveJpaSpecificationExecutor.java
│ │ ├── ReactivePagingAndSortingRepository.java
│ │ ├── auto
│ │ ├── ReactiveJpaRepositoriesAutoConfiguration.java
│ │ └── ReactiveJpaRepositoriesRegistrar1.java
│ │ ├── config
│ │ ├── EnableReactiveJpaRepositories.java
│ │ ├── ReactiveJpaRepositoriesRegistrar.java
│ │ └── ReactiveJpaRepositoryConfigExtension.java
│ │ ├── query
│ │ ├── AbstractQueryCreator.java
│ │ ├── AbstractReactiveJpaQuery.java
│ │ ├── AbstractStringBasedReactiveJpaQuery.java
│ │ ├── BasicParameterValueEvaluator.java
│ │ ├── DeclaredQuery.java
│ │ ├── DefaultQueryEnhancer.java
│ │ ├── DefaultReactiveJpaQueryExtractor.java
│ │ ├── EmptyDeclaredQuery.java
│ │ ├── ExpressionBasedStringQuery.java
│ │ ├── Jpa21Utils.java
│ │ ├── NamedQuery.java
│ │ ├── NativeReactiveJpaQuery.java
│ │ ├── ParameterBinder.java
│ │ ├── ParameterBinderFactory.java
│ │ ├── ParameterBinding.java
│ │ ├── ParameterMetadataProvider.java
│ │ ├── ParameterValueEvaluator.java
│ │ ├── PartTreeReactiveJpaQuery.java
│ │ ├── PredicateBuilder.java
│ │ ├── ProcedureParameter.java
│ │ ├── QueryEnhancer.java
│ │ ├── QueryEnhancerFactory.java
│ │ ├── QueryParameterSetter.java
│ │ ├── QueryParameterSetterFactory.java
│ │ ├── QueryUtils.java
│ │ ├── ReactiveJpaCountQueryCreator.java
│ │ ├── ReactiveJpaCriteriaDeleteQueryCreator.java
│ │ ├── ReactiveJpaCriteriaQueryCreator.java
│ │ ├── ReactiveJpaParameters.java
│ │ ├── ReactiveJpaParametersParameterAccessor.java
│ │ ├── ReactiveJpaQueryEnhancer.java
│ │ ├── ReactiveJpaQueryExecution.java
│ │ ├── ReactiveJpaQueryExecutionConverters.java
│ │ ├── ReactiveJpaQueryExtractor.java
│ │ ├── ReactiveJpaQueryFactory.java
│ │ ├── ReactiveJpaQueryLookupStrategy.java
│ │ ├── ReactiveJpaQueryMethod.java
│ │ ├── ReactiveJpaQueryMethodFactory.java
│ │ ├── ReactiveQueryRewriterProvider.java
│ │ ├── SimpleReactiveJpaQuery.java
│ │ ├── SpELParameterValueEvaluator.java
│ │ ├── StoredProcedureAttributeSource.java
│ │ ├── StoredProcedureAttributes.java
│ │ └── StringQuery.java
│ │ └── support
│ │ ├── CrudMethodMetadataContextHolder.java
│ │ ├── CrudMethodMetadataPostProcessor.java
│ │ ├── PersistenceExceptionHandlerPostProcessor.java
│ │ ├── ReactiveJpaRepositoryFactory.java
│ │ ├── ReactiveJpaRepositoryFactoryBean.java
│ │ ├── ReactiveJpaRepositoryImplementation.java
│ │ ├── SessionAwarePostProcessor.java
│ │ └── SimpleReactiveJpaRepository.java
│ └── jpa
│ ├── pu
│ └── CustomPersistenceUnitManager.java
│ ├── reactive
│ ├── EntityManagerFactoryBuilder.java
│ ├── EntityManagerFactoryBuilderCustomizer.java
│ ├── ReactiveHibernateJpaAutoConfiguration.java
│ ├── ReactiveHibernateJpaConfiguration.java
│ └── connection
│ │ ├── ConnectionFactoryUtils.java
│ │ ├── ConnectionHolder.java
│ │ ├── ConstantPool.java
│ │ ├── IsolationLevel.java
│ │ ├── Option.java
│ │ ├── ReactiveHibernateTransactionManager.java
│ │ ├── SessionContextHolder.java
│ │ ├── TransactionDefinition.java
│ │ └── TransactionUtils.java
│ └── support
│ └── EntityManagerFactoryBeanCreationExceptionFailureAnalyzer.java
└── resources
└── META-INF
├── spring.factories
└── spring
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
/.gitignore:
--------------------------------------------------------------------------------
1 | # These are some examples of commonly ignored file patterns.
2 | # You should customize this list as applicable to your project.
3 | # Learn more about .gitignore:
4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
5 |
6 | # Node artifact files
7 | node_modules/
8 | dist/
9 |
10 |
11 | # Compiled Java class files
12 | *.class
13 |
14 | # Compiled Python bytecode
15 | *.py[cod]
16 |
17 | # Log files
18 | *.log
19 |
20 | # Package files
21 | *.jar
22 |
23 | # Maven
24 | target/
25 |
26 | # JetBrains IDE
27 | .idea/
28 |
29 | # Unit test reports
30 | TEST*.xml
31 |
32 | # Generated by MacOS
33 | .DS_Store
34 |
35 | # Generated by Windows
36 | Thumbs.db
37 |
38 | # Applications
39 | *.app
40 | *.exe
41 | *.war
42 |
43 | # Large media files
44 | *.mp4
45 | *.tiff
46 | *.avi
47 | *.flv
48 | *.mov
49 | *.wmv
50 |
51 | /*.iml
52 | /src/main/resources/git.properties
53 | /.flattened-pom.xml
54 | /build/
55 | .settings/
56 | .vscode
57 |
--------------------------------------------------------------------------------
/LICENSE.MD:
--------------------------------------------------------------------------------
1 | Apache License
2 | ==============
3 |
4 | _Version 2.0, January 2004_
5 | _< >_
6 |
7 | ### Terms and Conditions for use, reproduction, and distribution
8 |
9 | #### 1. Definitions
10 |
11 | “License” shall mean the terms and conditions for use, reproduction, and
12 | distribution as defined by Sections 1 through 9 of this document.
13 |
14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright
15 | owner that is granting the License.
16 |
17 | “Legal Entity” shall mean the union of the acting entity and all other entities
18 | that control, are controlled by, or are under common control with that entity.
19 | For the purposes of this definition, “control” means **(i)** the power, direct or
20 | indirect, to cause the direction or management of such entity, whether by
21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22 | outstanding shares, or **(iii)** beneficial ownership of such entity.
23 |
24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising
25 | permissions granted by this License.
26 |
27 | “Source” form shall mean the preferred form for making modifications, including
28 | but not limited to software source code, documentation source, and configuration
29 | files.
30 |
31 | “Object” form shall mean any form resulting from mechanical transformation or
32 | translation of a Source form, including but not limited to compiled object code,
33 | generated documentation, and conversions to other media types.
34 |
35 | “Work” shall mean the work of authorship, whether in Source or Object form, made
36 | available under the License, as indicated by a copyright notice that is included
37 | in or attached to the work (an example is provided in the Appendix below).
38 |
39 | “Derivative Works” shall mean any work, whether in Source or Object form, that
40 | is based on (or derived from) the Work and for which the editorial revisions,
41 | annotations, elaborations, or other modifications represent, as a whole, an
42 | original work of authorship. For the purposes of this License, Derivative Works
43 | shall not include works that remain separable from, or merely link (or bind by
44 | name) to the interfaces of, the Work and Derivative Works thereof.
45 |
46 | “Contribution” shall mean any work of authorship, including the original version
47 | of the Work and any modifications or additions to that Work or Derivative Works
48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
49 | by the copyright owner or by an individual or Legal Entity authorized to submit
50 | on behalf of the copyright owner. For the purposes of this definition,
51 | “submitted” means any form of electronic, verbal, or written communication sent
52 | to the Licensor or its representatives, including but not limited to
53 | communication on electronic mailing lists, source code control systems, and
54 | issue tracking systems that are managed by, or on behalf of, the Licensor for
55 | the purpose of discussing and improving the Work, but excluding communication
56 | that is conspicuously marked or otherwise designated in writing by the copyright
57 | owner as “Not a Contribution.”
58 |
59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60 | of whom a Contribution has been received by Licensor and subsequently
61 | incorporated within the Work.
62 |
63 | #### 2. Grant of Copyright License
64 |
65 | Subject to the terms and conditions of this License, each Contributor hereby
66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67 | irrevocable copyright license to reproduce, prepare Derivative Works of,
68 | publicly display, publicly perform, sublicense, and distribute the Work and such
69 | Derivative Works in Source or Object form.
70 |
71 | #### 3. Grant of Patent License
72 |
73 | Subject to the terms and conditions of this License, each Contributor hereby
74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75 | irrevocable (except as stated in this section) patent license to make, have
76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77 | such license applies only to those patent claims licensable by such Contributor
78 | that are necessarily infringed by their Contribution(s) alone or by combination
79 | of their Contribution(s) with the Work to which such Contribution(s) was
80 | submitted. If You institute patent litigation against any entity (including a
81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82 | Contribution incorporated within the Work constitutes direct or contributory
83 | patent infringement, then any patent licenses granted to You under this License
84 | for that Work shall terminate as of the date such litigation is filed.
85 |
86 | #### 4. Redistribution
87 |
88 | You may reproduce and distribute copies of the Work or Derivative Works thereof
89 | in any medium, with or without modifications, and in Source or Object form,
90 | provided that You meet the following conditions:
91 |
92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93 | this License; and
94 | * **(b)** You must cause any modified files to carry prominent notices stating that You
95 | changed the files; and
96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97 | all copyright, patent, trademark, and attribution notices from the Source form
98 | of the Work, excluding those notices that do not pertain to any part of the
99 | Derivative Works; and
100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101 | Derivative Works that You distribute must include a readable copy of the
102 | attribution notices contained within such NOTICE file, excluding those notices
103 | that do not pertain to any part of the Derivative Works, in at least one of the
104 | following places: within a NOTICE text file distributed as part of the
105 | Derivative Works; within the Source form or documentation, if provided along
106 | with the Derivative Works; or, within a display generated by the Derivative
107 | Works, if and wherever such third-party notices normally appear. The contents of
108 | the NOTICE file are for informational purposes only and do not modify the
109 | License. You may add Your own attribution notices within Derivative Works that
110 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
111 | provided that such additional attribution notices cannot be construed as
112 | modifying the License.
113 |
114 | You may add Your own copyright statement to Your modifications and may provide
115 | additional or different license terms and conditions for use, reproduction, or
116 | distribution of Your modifications, or for any such Derivative Works as a whole,
117 | provided Your use, reproduction, and distribution of the Work otherwise complies
118 | with the conditions stated in this License.
119 |
120 | #### 5. Submission of Contributions
121 |
122 | Unless You explicitly state otherwise, any Contribution intentionally submitted
123 | for inclusion in the Work by You to the Licensor shall be under the terms and
124 | conditions of this License, without any additional terms or conditions.
125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
126 | any separate license agreement you may have executed with Licensor regarding
127 | such Contributions.
128 |
129 | #### 6. Trademarks
130 |
131 | This License does not grant permission to use the trade names, trademarks,
132 | service marks, or product names of the Licensor, except as required for
133 | reasonable and customary use in describing the origin of the Work and
134 | reproducing the content of the NOTICE file.
135 |
136 | #### 7. Disclaimer of Warranty
137 |
138 | Unless required by applicable law or agreed to in writing, Licensor provides the
139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141 | including, without limitation, any warranties or conditions of TITLE,
142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143 | solely responsible for determining the appropriateness of using or
144 | redistributing the Work and assume any risks associated with Your exercise of
145 | permissions under this License.
146 |
147 | #### 8. Limitation of Liability
148 |
149 | In no event and under no legal theory, whether in tort (including negligence),
150 | contract, or otherwise, unless required by applicable law (such as deliberate
151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
152 | liable to You for damages, including any direct, indirect, special, incidental,
153 | or consequential damages of any character arising as a result of this License or
154 | out of the use or inability to use the Work (including but not limited to
155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156 | any and all other commercial damages or losses), even if such Contributor has
157 | been advised of the possibility of such damages.
158 |
159 | #### 9. Accepting Warranty or Additional Liability
160 |
161 | While redistributing the Work or Derivative Works thereof, You may choose to
162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163 | other liability obligations and/or rights consistent with this License. However,
164 | in accepting such obligations, You may act only on Your own behalf and on Your
165 | sole responsibility, not on behalf of any other Contributor, and only if You
166 | agree to indemnify, defend, and hold each Contributor harmless for any liability
167 | incurred by, or claims asserted against, such Contributor by reason of your
168 | accepting any such warranty or additional liability.
169 |
170 | _END OF TERMS AND CONDITIONS_
171 |
172 | ### APPENDIX: How to apply the Apache License to your work
173 |
174 | To apply the Apache License to your work, attach the following boilerplate
175 | notice, with the fields enclosed by brackets `[]` replaced with your own
176 | identifying information. (Don't include the brackets!) The text should be
177 | enclosed in the appropriate comment syntax for the file format. We also
178 | recommend that a file or class name and description of purpose be included on
179 | the same “printed page” as the copyright notice for easier identification within
180 | third-party archives.
181 |
182 | Copyright [yyyy] [name of copyright owner]
183 |
184 | Licensed under the Apache License, Version 2.0 (the "License");
185 | you may not use this file except in compliance with the License.
186 | You may obtain a copy of the License at
187 |
188 | http://www.apache.org/licenses/LICENSE-2.0
189 |
190 | Unless required by applicable law or agreed to in writing, software
191 | distributed under the License is distributed on an "AS IS" BASIS,
192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
193 | See the License for the specific language governing permissions and
194 | limitations under the License.
--------------------------------------------------------------------------------
/images/crud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anaconda875/reactive-hibernate-spring-boot-starter/26e51c23f1e970a9da3af09e0b846665dbb9c293/images/crud.png
--------------------------------------------------------------------------------
/images/query.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anaconda875/reactive-hibernate-spring-boot-starter/26e51c23f1e970a9da3af09e0b846665dbb9c293/images/query.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 3.2.0
10 |
11 |
12 | io.github.anaconda875
13 | reactive-hibernate-spring-boot-starter
14 | 1.1.1
15 | Integrate Reactive Hibernate to Spring Data with Auto-config
16 |
17 |
18 |
19 | Apache License, Version 2.0
20 | http://www.apache.org/licenses/LICENSE-2.0.txt
21 | repo
22 |
23 |
24 |
25 |
26 |
27 | anaconda875
28 | Bao Ngo
29 | hflbtmax@gmail.com
30 |
31 |
32 |
33 |
34 | scm:git:git@github.com:anaconda875/reactive-hibernate-spring-boot-starter.git
35 | scm:git:git@github.com:anaconda875/reactive-hibernate-spring-boot-starter.git
36 |
37 | https://github.com/anaconda875/reactive-hibernate-spring-boot-starter
38 |
39 |
40 |
41 | 17
42 |
43 | 2.2.2.Final
44 | 4.5.3
45 | 6.4.4.Final
46 |
47 |
48 | 3.0.0-M5
49 | 3.0.0-M5
50 |
51 | false
52 | true
53 |
54 |
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-starter
59 |
60 |
61 | org.springframework
62 | spring-orm
63 |
64 |
65 | org.springframework.data
66 | spring-data-jpa
67 |
68 |
69 | io.projectreactor
70 | reactor-core
71 |
72 |
73 |
74 | org.hibernate.reactive
75 | hibernate-reactive-core
76 | ${hibernate-reactive.version}
77 |
78 |
79 | org.apache.commons
80 | commons-lang3
81 | 3.14.0
82 |
83 |
84 | org.apache.commons
85 | commons-collections4
86 | 4.5.0-M1
87 |
88 |
89 |
90 | org.springframework.boot
91 | spring-boot-starter-test
92 | test
93 |
94 |
95 |
96 |
97 |
98 | ossrh
99 |
100 |
101 |
102 | org.sonatype.central
103 | central-publishing-maven-plugin
104 | 0.4.0
105 | true
106 |
107 | central
108 | true
109 |
110 |
111 |
112 | org.apache.maven.plugins
113 | maven-gpg-plugin
114 | 1.6
115 |
116 |
117 | sign-artifacts
118 | verify
119 |
120 | sign
121 |
122 |
123 |
125 |
126 | --pinentry-mode
127 | loopback
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | com.spotify.fmt
143 | fmt-maven-plugin
144 | 2.21
145 |
146 |
147 |
148 | format
149 |
150 |
151 |
152 |
153 |
154 | org.apache.maven.plugins
155 | maven-surefire-plugin
156 | ${maven-surefire-plugin.version}
157 |
158 | true
159 |
160 |
161 |
162 | test
163 | unit-test
164 |
167 |
168 |
169 | **/*IT.java
170 |
171 |
172 |
173 |
174 |
175 |
176 | org.apache.maven.plugins
177 | maven-failsafe-plugin
178 | ${maven-failsafe-plugin.version}
179 |
180 | true
181 |
182 |
183 |
184 | integration-test
185 | integration-test
186 |
190 |
191 |
192 | **/*IT.java
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | org.apache.maven.plugins
201 | maven-source-plugin
202 | 3.2.1
203 |
204 |
205 | attach-sources
206 |
207 | jar-no-fork
208 |
209 |
210 |
211 |
212 |
213 | org.apache.maven.plugins
214 | maven-javadoc-plugin
215 | 3.2.0
216 |
217 |
218 | attach-javadocs
219 |
220 | jar
221 |
222 |
223 |
224 |
225 | ${java.home}/bin/javadoc
226 |
227 |
228 |
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/config/EnableReactiveJpaAuditing.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.config;
2 |
3 | import java.lang.annotation.*;
4 | import org.springframework.context.annotation.Import;
5 |
6 | /**
7 | * @author Bao.Ngo
8 | */
9 | @Inherited
10 | @Documented
11 | @Target(ElementType.TYPE)
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @Import(ReactiveJpaAuditingRegistrar.class)
14 | public @interface EnableReactiveJpaAuditing {
15 |
16 | String auditorAwareRef() default "";
17 |
18 | boolean setDates() default true;
19 |
20 | boolean modifyOnCreate() default true;
21 |
22 | String dateTimeProviderRef() default "";
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/config/ReactiveJpaAuditingRegistrar.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.config;
2 |
3 | import com.htech.data.jpa.reactive.mapping.event.ReactiveAuditingEntityCallback;
4 | import java.lang.annotation.Annotation;
5 | import org.springframework.beans.factory.config.BeanDefinition;
6 | import org.springframework.beans.factory.support.BeanDefinitionBuilder;
7 | import org.springframework.beans.factory.support.BeanDefinitionRegistry;
8 | import org.springframework.beans.factory.support.RootBeanDefinition;
9 | import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
10 | import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
11 | import org.springframework.data.auditing.config.AuditingConfiguration;
12 | import org.springframework.data.config.ParsingUtils;
13 | import org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean;
14 | import org.springframework.util.Assert;
15 |
16 | /**
17 | * @author Bao.Ngo
18 | */
19 | class ReactiveJpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
20 |
21 | private static final String JPA_MAPPING_CONTEXT_BEAN_NAME = "jpaMappingContext";
22 |
23 | @Override
24 | protected Class extends Annotation> getAnnotation() {
25 | return EnableReactiveJpaAuditing.class;
26 | }
27 |
28 | @Override
29 | protected String getAuditingHandlerBeanName() {
30 | return "reactiveJpaAuditingHandler";
31 | }
32 |
33 | @Override
34 | protected void postProcess(
35 | BeanDefinitionBuilder builder,
36 | AuditingConfiguration configuration,
37 | BeanDefinitionRegistry registry) {
38 | builder.setFactoryMethod("from").addConstructorArgReference(JPA_MAPPING_CONTEXT_BEAN_NAME);
39 | }
40 |
41 | @Override
42 | protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(
43 | AuditingConfiguration configuration) {
44 | Assert.notNull(configuration, "AuditingConfiguration must not be null");
45 |
46 | return configureDefaultAuditHandlerAttributes(
47 | configuration,
48 | BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class));
49 | }
50 |
51 | @Override
52 | protected void registerAuditListenerBeanDefinition(
53 | BeanDefinition auditingHandlerDefinition, BeanDefinitionRegistry registry) {
54 | Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null");
55 | Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
56 |
57 | if (!registry.containsBeanDefinition(JPA_MAPPING_CONTEXT_BEAN_NAME)) {
58 | registry.registerBeanDefinition(
59 | JPA_MAPPING_CONTEXT_BEAN_NAME, //
60 | new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class));
61 | }
62 |
63 | BeanDefinitionBuilder listenerBeanDefinitionBuilder =
64 | BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
65 | listenerBeanDefinitionBuilder.addConstructorArgValue(
66 | ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
67 |
68 | registerInfrastructureBeanWithId(
69 | listenerBeanDefinitionBuilder.getBeanDefinition(),
70 | ReactiveAuditingEntityCallback.class.getName(),
71 | registry);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/core/ReactiveJpaDataAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.core;
2 |
3 | import com.htech.jpa.reactive.ReactiveHibernateJpaAutoConfiguration;
4 | import org.hibernate.reactive.stage.Stage;
5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | @Configuration(proxyBeanMethods = false)
15 | @ConditionalOnClass({Stage.SessionFactory.class})
16 | @AutoConfigureAfter(ReactiveHibernateJpaAutoConfiguration.class)
17 | public class ReactiveJpaDataAutoConfiguration {
18 |
19 | @Bean
20 | @ConditionalOnMissingBean
21 | public StageReactiveJpaEntityOperations reactiveJpaEntityTemplate(
22 | Stage.SessionFactory sessionFactory) {
23 | return new StageReactiveJpaEntityTemplate(sessionFactory);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/core/StageReactiveJpaEntityOperations.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.core;
2 |
3 | import org.hibernate.reactive.stage.Stage;
4 | import reactor.core.publisher.Flux;
5 | import reactor.core.publisher.Mono;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | public interface StageReactiveJpaEntityOperations {
11 |
12 | Mono persist(T entity);
13 |
14 | Flux persist(Iterable entity);
15 |
16 | Stage.SessionFactory sessionFactory();
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/core/StageReactiveJpaEntityTemplate.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.core;
2 |
3 | import static reactor.core.scheduler.Schedulers.DEFAULT_POOL_SIZE;
4 |
5 | import com.htech.data.jpa.reactive.mapping.event.BeforeSaveCallback;
6 | import com.htech.jpa.reactive.connection.SessionContextHolder;
7 | import org.apache.commons.collections4.IterableUtils;
8 | import org.hibernate.reactive.stage.Stage;
9 | import org.springframework.beans.BeansException;
10 | import org.springframework.context.ApplicationContext;
11 | import org.springframework.context.ApplicationContextAware;
12 | import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
13 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
14 | import reactor.core.publisher.Flux;
15 | import reactor.core.publisher.Mono;
16 |
17 | /**
18 | * @author Bao.Ngo
19 | */
20 | public class StageReactiveJpaEntityTemplate
21 | implements StageReactiveJpaEntityOperations, ApplicationContextAware {
22 |
23 | private static final ThreadPoolTaskExecutor EXECUTOR;
24 |
25 | private final Stage.SessionFactory sessionFactory;
26 | private ReactiveEntityCallbacks entityCallbacks;
27 |
28 | static {
29 | EXECUTOR = new ThreadPoolTaskExecutor();
30 | EXECUTOR.setCorePoolSize(DEFAULT_POOL_SIZE);
31 | EXECUTOR.setMaxPoolSize(DEFAULT_POOL_SIZE * 2);
32 | EXECUTOR.setThreadNamePrefix("custom-parallel-");
33 | EXECUTOR.initialize();
34 | }
35 |
36 | public StageReactiveJpaEntityTemplate(Stage.SessionFactory sessionFactory) {
37 | this.sessionFactory = sessionFactory;
38 | }
39 |
40 | @Override
41 | public Mono persist(T entity) {
42 | return doInsert(entity);
43 | }
44 |
45 | @Override
46 | public Flux persist(Iterable entities) {
47 | return doInsert(entities);
48 | }
49 |
50 | private Flux doInsert(Iterable entities) {
51 | if (IterableUtils.isEmpty(entities)) {
52 | return Flux.empty();
53 | }
54 |
55 | return SessionContextHolder.currentSession()
56 | .flatMap(
57 | session ->
58 | Flux.fromIterable(entities)
59 | .concatMap(this::maybeCallBeforeSave)
60 | .collectList()
61 | .flatMap(
62 | list ->
63 | Mono.defer(
64 | () ->
65 | Mono.fromCompletionStage(session.persist(list.toArray()))
66 | .then(deferFlushing(session))
67 | .thenReturn(list))))
68 | .flatMapMany(Flux::fromIterable);
69 | }
70 |
71 | private Mono doInsert(T entity) {
72 | return maybeCallBeforeSave(entity)
73 | .flatMap(
74 | e ->
75 | SessionContextHolder.currentSession()
76 | .flatMap(
77 | session ->
78 | Mono.defer(
79 | () ->
80 | Mono.fromCompletionStage(session.persist(e))
81 | .then(deferFlushing(session))
82 | .thenReturn(e))));
83 |
84 | // Mono tMono = test.flatMap(v -> {
85 | // return this.maybeCallBeforeSave(entity);
86 | // });
87 | // return tMono.as(e -> {
88 | // return toWrapper(e, Uni.class).chain(t -> session
89 | // .persist(t)
90 | // .chain(session::flush)
91 | // .replaceWith(t)
92 | // .emitOn(EXECUTOR)
93 | // .runSubscriptionOn(EXECUTOR));
94 | // })
95 | // ;
96 | }
97 |
98 | private Mono maybeCallBeforeSave(T entity) {
99 | if (entityCallbacks != null) {
100 | return entityCallbacks.callback(BeforeSaveCallback.class, entity);
101 | }
102 |
103 | return Mono.just(entity);
104 | }
105 |
106 | private static Mono deferFlushing(Stage.Session session) {
107 | return Mono.defer(() -> Mono.fromCompletionStage(session.flush()));
108 | }
109 |
110 | @Override
111 | public Stage.SessionFactory sessionFactory() {
112 | return sessionFactory;
113 | }
114 |
115 | @Override
116 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
117 | if (entityCallbacks == null) {
118 | setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
119 | }
120 | }
121 |
122 | public void setEntityCallbacks(ReactiveEntityCallbacks entityCallbacks) {
123 | this.entityCallbacks = entityCallbacks;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/mapping/event/BeforeSaveCallback.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.mapping.event;
2 |
3 | import org.reactivestreams.Publisher;
4 | import org.springframework.data.mapping.callback.EntityCallback;
5 |
6 | /**
7 | * @author Bao.Ngo
8 | */
9 | @FunctionalInterface
10 | public interface BeforeSaveCallback extends EntityCallback {
11 |
12 | Publisher onBeforeConvert(T entity);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/mapping/event/ReactiveAuditingEntityCallback.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.mapping.event;
2 |
3 | import org.reactivestreams.Publisher;
4 | import org.springframework.beans.factory.ObjectFactory;
5 | import org.springframework.core.Ordered;
6 | import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
7 | import org.springframework.util.Assert;
8 |
9 | /**
10 | * @author Bao.Ngo
11 | */
12 | public class ReactiveAuditingEntityCallback implements BeforeSaveCallback, Ordered {
13 |
14 | private final ObjectFactory auditingHandlerFactory;
15 |
16 | public ReactiveAuditingEntityCallback(
17 | ObjectFactory auditingHandlerFactory) {
18 | Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null");
19 | this.auditingHandlerFactory = auditingHandlerFactory;
20 | }
21 |
22 | @Override
23 | public Publisher onBeforeConvert(Object entity) {
24 | return auditingHandlerFactory.getObject().markAudited(entity);
25 | }
26 |
27 | @Override
28 | public int getOrder() {
29 | return 100;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/ReactiveCrudRepository.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository;
2 |
3 | import org.springframework.data.jpa.repository.Modifying;
4 | import org.springframework.data.repository.NoRepositoryBean;
5 | import org.springframework.data.repository.Repository;
6 | import org.springframework.transaction.annotation.Transactional;
7 | import reactor.core.publisher.Flux;
8 | import reactor.core.publisher.Mono;
9 |
10 | /**
11 | * @author Bao.Ngo
12 | */
13 | @NoRepositoryBean
14 | public interface ReactiveCrudRepository extends Repository {
15 | Flux findAll();
16 |
17 | // Uni findById(ID id);
18 | Mono findById(ID id);
19 |
20 | Mono getReferenceById(ID id);
21 |
22 | @Transactional
23 | @Modifying
24 | Mono save(S entity);
25 |
26 | @Transactional
27 | Flux saveAll(Iterable entities);
28 |
29 | Mono existsById(ID id);
30 |
31 | Flux findAllById(Iterable ids);
32 |
33 | Mono count();
34 |
35 | @Transactional
36 | Mono deleteById(ID id);
37 |
38 | @Transactional
39 | Mono delete(T entity);
40 |
41 | @Transactional
42 | Mono deleteAllById(Iterable extends ID> ids);
43 |
44 | @Transactional
45 | Mono deleteAll(Iterable extends T> entities);
46 |
47 | @Transactional
48 | Mono deleteAll();
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/ReactiveJpaRepository.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository;
2 |
3 | import org.springframework.data.repository.NoRepositoryBean;
4 |
5 | /**
6 | * @author Bao.Ngo
7 | */
8 | @NoRepositoryBean
9 | public interface ReactiveJpaRepository
10 | extends ReactiveCrudRepository, ReactivePagingAndSortingRepository {}
11 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/ReactiveJpaSpecificationExecutor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository;
2 |
3 | import java.util.List;
4 | import org.springframework.data.domain.Page;
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.domain.Sort;
7 | import org.springframework.data.jpa.domain.Specification;
8 | import reactor.core.publisher.Flux;
9 | import reactor.core.publisher.Mono;
10 |
11 | public interface ReactiveJpaSpecificationExecutor {
12 |
13 | Mono findOne(Specification spec);
14 |
15 | Flux findAll(Specification spec);
16 |
17 | Flux findAll(Specification spec, Sort sort);
18 |
19 | Mono> findAllToList(Specification spec);
20 |
21 | Mono> findAll(Specification spec, Pageable pageable);
22 |
23 | Mono count(Specification spec);
24 |
25 | Mono exists(Specification spec);
26 |
27 | Mono delete(Specification spec);
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/ReactivePagingAndSortingRepository.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository;
2 |
3 | import org.springframework.data.domain.Page;
4 | import org.springframework.data.domain.Pageable;
5 | import org.springframework.data.domain.Sort;
6 | import org.springframework.data.repository.NoRepositoryBean;
7 | import org.springframework.data.repository.Repository;
8 | import reactor.core.publisher.Flux;
9 | import reactor.core.publisher.Mono;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | @NoRepositoryBean
15 | public interface ReactivePagingAndSortingRepository extends Repository {
16 |
17 | Flux findAll(Sort sort);
18 |
19 | Mono> findAll(Pageable pageable);
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/auto/ReactiveJpaRepositoriesAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.auto;
2 |
3 | import com.htech.jpa.reactive.ReactiveHibernateJpaAutoConfiguration;
4 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
5 | import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.context.annotation.Import;
8 | import org.springframework.context.annotation.ImportSelector;
9 | import org.springframework.core.type.AnnotationMetadata;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | @Configuration(proxyBeanMethods = false)
15 | @AutoConfigureAfter({
16 | ReactiveHibernateJpaAutoConfiguration.class,
17 | TaskExecutionAutoConfiguration.class
18 | })
19 | @Import(ReactiveJpaRepositoriesAutoConfiguration.ReactiveJpaRepositoriesImportSelector.class)
20 | public class ReactiveJpaRepositoriesAutoConfiguration {
21 |
22 | static class ReactiveJpaRepositoriesImportSelector implements ImportSelector {
23 |
24 | @Override
25 | public String[] selectImports(AnnotationMetadata importingClassMetadata) {
26 | return new String[] {determineImport()};
27 | }
28 |
29 | private String determineImport() {
30 | return ReactiveJpaRepositoriesRegistrar1.class.getName();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/auto/ReactiveJpaRepositoriesRegistrar1.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.auto;
2 |
3 | import com.htech.data.jpa.reactive.repository.config.EnableReactiveJpaRepositories;
4 | import com.htech.data.jpa.reactive.repository.config.ReactiveJpaRepositoryConfigExtension;
5 | import java.lang.annotation.Annotation;
6 | import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport;
7 | import org.springframework.data.repository.config.RepositoryConfigurationExtension;
8 |
9 | /**
10 | * @author Bao.Ngo
11 | */
12 | class ReactiveJpaRepositoriesRegistrar1 extends AbstractRepositoryConfigurationSourceSupport {
13 |
14 | @Override
15 | protected Class extends Annotation> getAnnotation() {
16 | return EnableReactiveJpaRepositories.class;
17 | }
18 |
19 | @Override
20 | protected Class> getConfiguration() {
21 | return EnableReactiveRepositoriesConfiguration.class;
22 | }
23 |
24 | @Override
25 | protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
26 | return new ReactiveJpaRepositoryConfigExtension();
27 | }
28 |
29 | @EnableReactiveJpaRepositories
30 | private static class EnableReactiveRepositoriesConfiguration {}
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/config/EnableReactiveJpaRepositories.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.config;
2 |
3 | import com.htech.data.jpa.reactive.repository.support.ReactiveJpaRepositoryFactoryBean;
4 | import java.lang.annotation.*;
5 | import org.springframework.context.annotation.ComponentScan;
6 | import org.springframework.context.annotation.Import;
7 | import org.springframework.data.repository.config.BootstrapMode;
8 | import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
9 | import org.springframework.data.repository.query.QueryLookupStrategy;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | @Target(ElementType.TYPE)
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Documented
17 | @Inherited
18 | @Import(ReactiveJpaRepositoriesRegistrar.class)
19 | public @interface EnableReactiveJpaRepositories {
20 |
21 | String[] value() default {};
22 |
23 | String[] basePackages() default {};
24 |
25 | Class>[] basePackageClasses() default {};
26 |
27 | ComponentScan.Filter[] includeFilters() default {};
28 |
29 | ComponentScan.Filter[] excludeFilters() default {};
30 |
31 | String repositoryImplementationPostfix() default "Impl";
32 |
33 | String namedQueriesLocation() default "";
34 |
35 | QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;
36 |
37 | Class> repositoryFactoryBeanClass() default ReactiveJpaRepositoryFactoryBean.class;
38 |
39 | Class> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
40 |
41 | // JPA specific configuration
42 |
43 | String sessionFactoryRef() default "sessionFactory";
44 |
45 | String reactiveJpaEntityOperationsRef() default "reactiveJpaEntityTemplate";
46 |
47 | boolean considerNestedRepositories() default false;
48 |
49 | boolean enableDefaultTransactions() default true;
50 |
51 | BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
52 |
53 | char escapeCharacter() default '\\';
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/config/ReactiveJpaRepositoriesRegistrar.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.config;
2 |
3 | import java.lang.annotation.Annotation;
4 | import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
5 | import org.springframework.data.repository.config.RepositoryConfigurationExtension;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | class ReactiveJpaRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
11 |
12 | @Override
13 | protected Class extends Annotation> getAnnotation() {
14 | return EnableReactiveJpaRepositories.class;
15 | }
16 |
17 | @Override
18 | protected RepositoryConfigurationExtension getExtension() {
19 | return new ReactiveJpaRepositoryConfigExtension();
20 | }
21 |
22 | // @Override
23 | // protected Class> getConfiguration() {
24 | // return EnableReactiveRepositoriesConfiguration.class;
25 | // }
26 |
27 | // @Override
28 | // protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
29 | // return new ReactiveJpaRepositoryConfigExtension();
30 | // }
31 |
32 | @EnableReactiveJpaRepositories
33 | private static class EnableReactiveRepositoriesConfiguration {}
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/config/ReactiveJpaRepositoryConfigExtension.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.config;
2 |
3 | import com.htech.data.jpa.reactive.repository.support.ReactiveJpaRepositoryFactoryBean;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.MappedSuperclass;
6 | import java.lang.annotation.Annotation;
7 | import java.util.*;
8 | import org.springframework.beans.factory.support.BeanDefinitionBuilder;
9 | import org.springframework.beans.factory.support.BeanDefinitionRegistry;
10 | import org.springframework.beans.factory.support.RootBeanDefinition;
11 | import org.springframework.core.annotation.AnnotationAttributes;
12 | import org.springframework.data.jpa.repository.JpaRepository;
13 | import org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean;
14 | import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;
15 | import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
16 | import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
17 | import org.springframework.data.repository.config.RepositoryConfigurationSource;
18 | import org.springframework.data.repository.core.RepositoryMetadata;
19 |
20 | /**
21 | * @author Bao.Ngo
22 | */
23 | public class ReactiveJpaRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
24 |
25 | private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME =
26 | "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
27 | private static final String JPA_MAPPING_CONTEXT_BEAN_NAME = "jpaMappingContext";
28 | private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
29 |
30 | @Override
31 | public String getModuleName() {
32 | return "REACTIVE_JPA";
33 | }
34 |
35 | @Override
36 | public String getRepositoryFactoryBeanClassName() {
37 | return ReactiveJpaRepositoryFactoryBean.class.getName();
38 | }
39 |
40 | @Override
41 | protected String getModulePrefix() {
42 | return getModuleName().toLowerCase(Locale.US);
43 | }
44 |
45 | @Override
46 | protected Collection> getIdentifyingAnnotations() {
47 | return Arrays.asList(Entity.class, MappedSuperclass.class);
48 | }
49 |
50 | @Override
51 | protected Collection> getIdentifyingTypes() {
52 | return Collections.>singleton(JpaRepository.class);
53 | }
54 |
55 | @Override
56 | public void postProcess(
57 | BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
58 | AnnotationAttributes attributes = config.getAttributes();
59 |
60 | String reactiveJpaEntityOperationsRef = attributes.getString("reactiveJpaEntityOperationsRef");
61 | // if (StringUtils.hasText(reactiveJpaEntityOperationsRef)) {
62 | builder.addPropertyReference("entityOperations", reactiveJpaEntityOperationsRef);
63 | builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(config).orElse('\\'));
64 | // } else {
65 | // //TODO
66 | // }
67 | }
68 |
69 | private static Optional getEscapeCharacter(RepositoryConfigurationSource source) {
70 | try {
71 | return source.getAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class);
72 | } catch (IllegalArgumentException ___) {
73 | return Optional.empty();
74 | }
75 | }
76 |
77 | @Override
78 | public void registerBeansForRoot(
79 | BeanDefinitionRegistry registry, RepositoryConfigurationSource config) {
80 |
81 | super.registerBeansForRoot(registry, config);
82 |
83 | // registerSharedEntityMangerIfNotAlreadyRegistered(registry, config);
84 |
85 | Object source = config.getSource();
86 |
87 | // registerLazyIfNotAlreadyRegistered(
88 | // () -> new RootBeanDefinition(EntityManagerBeanDefinitionRegistrarPostProcessor.class),
89 | // registry,
90 | // EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME, source);
91 |
92 | registerLazyIfNotAlreadyRegistered(
93 | () -> new RootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class),
94 | registry,
95 | JPA_MAPPING_CONTEXT_BEAN_NAME,
96 | source);
97 |
98 | // registerLazyIfNotAlreadyRegistered(() -> new RootBeanDefinition(PAB_POST_PROCESSOR),
99 | // registry,
100 | // AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME, source);
101 |
102 | // Register bean definition for DefaultJpaContext
103 |
104 | // registerLazyIfNotAlreadyRegistered(() -> {
105 | //
106 | // RootBeanDefinition contextDefinition = new RootBeanDefinition(DefaultJpaContext.class);
107 | // contextDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
108 | //
109 | // return contextDefinition;
110 | //
111 | // }, registry, JPA_CONTEXT_BEAN_NAME, source);
112 |
113 | registerIfNotAlreadyRegistered(
114 | () -> new RootBeanDefinition(JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME),
115 | registry,
116 | JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME,
117 | source);
118 |
119 | // EvaluationContextExtension for JPA specific SpEL functions
120 |
121 | registerIfNotAlreadyRegistered(
122 | () -> {
123 | Object value =
124 | AnnotationRepositoryConfigurationSource.class.isInstance(config) //
125 | ? config.getRequiredAttribute(ESCAPE_CHARACTER_PROPERTY, Character.class) //
126 | : config.getAttribute(ESCAPE_CHARACTER_PROPERTY).orElse("\\");
127 |
128 | BeanDefinitionBuilder builder =
129 | BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class);
130 | builder.addConstructorArgValue(value);
131 |
132 | return builder.getBeanDefinition();
133 | },
134 | registry,
135 | JpaEvaluationContextExtension.class.getName(),
136 | source);
137 | }
138 |
139 | @Override
140 | protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) {
141 | return metadata.isReactiveRepository();
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/AbstractQueryCreator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Collections;
4 | import java.util.Iterator;
5 | import java.util.List;
6 | import java.util.Optional;
7 | import org.springframework.data.domain.Sort;
8 | import org.springframework.data.repository.query.ParameterAccessor;
9 | import org.springframework.data.repository.query.parser.Part;
10 | import org.springframework.data.repository.query.parser.PartTree;
11 | import org.springframework.lang.Nullable;
12 | import org.springframework.util.Assert;
13 |
14 | /**
15 | * @author Bao.Ngo
16 | */
17 | public abstract class AbstractQueryCreator {
18 |
19 | protected final Optional parameters;
20 | protected final PartTree tree;
21 |
22 | public AbstractQueryCreator(PartTree tree) {
23 | this(tree, Optional.empty());
24 | }
25 |
26 | public AbstractQueryCreator(PartTree tree, ParameterAccessor parameters) {
27 | this(tree, Optional.of(parameters));
28 | }
29 |
30 | private AbstractQueryCreator(PartTree tree, Optional parameters) {
31 | Assert.notNull(tree, "PartTree must not be null");
32 | Assert.notNull(parameters, "ParameterAccessor must not be null");
33 |
34 | this.tree = tree;
35 | this.parameters = parameters;
36 | }
37 |
38 | public abstract List> getParameterExpressions();
39 |
40 | public T createQuery() {
41 | return createQuery(
42 | parameters
43 | .map(ParameterAccessor::getSort) //
44 | .orElse(Sort.unsorted()));
45 | }
46 |
47 | public T createQuery(Sort dynamicSort) {
48 | Assert.notNull(dynamicSort, "DynamicSort must not be null");
49 | return complete(createCriteria(tree), tree.getSort().and(dynamicSort));
50 | }
51 |
52 | @Nullable
53 | private S createCriteria(PartTree tree) {
54 | S base = null;
55 | Iterator iterator =
56 | parameters.map(ParameterAccessor::iterator).orElse(Collections.emptyIterator());
57 |
58 | for (PartTree.OrPart node : tree) {
59 | Iterator parts = node.iterator();
60 |
61 | if (!parts.hasNext()) {
62 | throw new IllegalStateException(String.format("No part found in PartTree %s", tree));
63 | }
64 |
65 | S criteria = create(parts.next(), iterator);
66 | while (parts.hasNext()) {
67 | criteria = and(parts.next(), criteria, iterator);
68 | }
69 |
70 | base = base == null ? criteria : or(base, criteria);
71 | }
72 |
73 | return base;
74 | }
75 |
76 | protected abstract S create(Part part, Iterator iterator);
77 |
78 | protected abstract S and(Part part, S base, Iterator iterator);
79 |
80 | protected abstract S or(S base, S criteria);
81 |
82 | protected abstract T complete(@Nullable S criteria, Sort sort);
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/BasicParameterValueEvaluator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Optional;
4 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
5 | import reactor.core.publisher.Mono;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | public class BasicParameterValueEvaluator implements ParameterValueEvaluator {
11 |
12 | private final ReactiveJpaParameters.JpaParameter parameter;
13 |
14 | public BasicParameterValueEvaluator(ReactiveJpaParameters.JpaParameter parameter) {
15 | this.parameter = parameter;
16 | }
17 |
18 | @Override
19 | public Mono> evaluate(JpaParametersParameterAccessor accessor) {
20 | return Mono.just(accessor).map(a -> Optional.ofNullable(a.getValue(parameter)));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/DeclaredQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.List;
4 | import org.springframework.lang.Nullable;
5 | import org.springframework.util.ObjectUtils;
6 |
7 | public interface DeclaredQuery {
8 | static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
9 | return ObjectUtils.isEmpty(query)
10 | ? EmptyDeclaredQuery.EMPTY_QUERY
11 | : new StringQuery(query, nativeQuery);
12 | }
13 |
14 | boolean hasNamedParameter();
15 |
16 | boolean usesJdbcStyleParameters();
17 |
18 | String getQueryString();
19 |
20 | @Nullable
21 | String getAlias();
22 |
23 | boolean hasConstructorExpression();
24 |
25 | boolean isDefaultProjection();
26 |
27 | List getParameterBindings();
28 |
29 | DeclaredQuery deriveCountQuery(
30 | @Nullable String countQuery, @Nullable String countQueryProjection);
31 |
32 | default boolean usesPaging() {
33 | return false;
34 | }
35 |
36 | default boolean isNativeQuery() {
37 | return false;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/DefaultQueryEnhancer.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Set;
4 | import org.springframework.data.domain.Sort;
5 | import org.springframework.lang.Nullable;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | public class DefaultQueryEnhancer implements QueryEnhancer {
11 |
12 | protected final DeclaredQuery query;
13 |
14 | public DefaultQueryEnhancer(DeclaredQuery query) {
15 | this.query = query;
16 | }
17 |
18 | @Override
19 | public String applySorting(Sort sort, @Nullable String alias) {
20 | return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
21 | }
22 |
23 | @Override
24 | public String detectAlias() {
25 | return QueryUtils.detectAlias(this.query.getQueryString());
26 | }
27 |
28 | @Override
29 | public String createCountQueryFor(@Nullable String countProjection) {
30 | return QueryUtils.createCountQueryFor(
31 | this.query.getQueryString(), countProjection, this.query.isNativeQuery());
32 | }
33 |
34 | @Override
35 | public String getProjection() {
36 | return QueryUtils.getProjection(this.query.getQueryString());
37 | }
38 |
39 | @Override
40 | public Set getJoinAliases() {
41 | return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
42 | }
43 |
44 | @Override
45 | public DeclaredQuery getQuery() {
46 | return this.query;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/DefaultReactiveJpaQueryExtractor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.lang.reflect.Field;
4 | import org.hibernate.reactive.query.ReactiveQuery;
5 | import org.hibernate.reactive.stage.Stage;
6 | import org.hibernate.reactive.stage.impl.StageQueryImpl;
7 | import org.springframework.data.util.ReflectionUtils;
8 |
9 | /**
10 | * @author Bao.Ngo
11 | */
12 | public class DefaultReactiveJpaQueryExtractor implements ReactiveJpaQueryExtractor {
13 |
14 | @Override
15 | public String extractQueryString(Stage.AbstractQuery query) {
16 | if (query instanceof StageQueryImpl> sqi) {
17 | try {
18 | Field delegate = ReflectionUtils.findRequiredField(StageQueryImpl.class, "delegate");
19 | org.springframework.util.ReflectionUtils.makeAccessible(delegate);
20 | ReactiveQuery q = (ReactiveQuery) delegate.get(sqi);
21 | return q.getQueryString();
22 | } catch (IllegalAccessException e) {
23 | throw new RuntimeException(e);
24 | }
25 | }
26 |
27 | throw new IllegalArgumentException("Don't know how to extract the query string from " + query);
28 | }
29 |
30 | @Override
31 | public boolean canExtractQuery() {
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/EmptyDeclaredQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 | import org.springframework.lang.Nullable;
6 | import org.springframework.util.Assert;
7 |
8 | public class EmptyDeclaredQuery implements DeclaredQuery {
9 |
10 | static final DeclaredQuery EMPTY_QUERY = new EmptyDeclaredQuery();
11 |
12 | @Override
13 | public boolean hasNamedParameter() {
14 | return false;
15 | }
16 |
17 | @Override
18 | public boolean usesJdbcStyleParameters() {
19 | return false;
20 | }
21 |
22 | @Override
23 | public String getQueryString() {
24 | return "";
25 | }
26 |
27 | @Override
28 | public String getAlias() {
29 | return null;
30 | }
31 |
32 | @Override
33 | public boolean hasConstructorExpression() {
34 | return false;
35 | }
36 |
37 | @Override
38 | public boolean isDefaultProjection() {
39 | return false;
40 | }
41 |
42 | @Override
43 | public List getParameterBindings() {
44 | return Collections.emptyList();
45 | }
46 |
47 | @Override
48 | public DeclaredQuery deriveCountQuery(
49 | @Nullable String countQuery, @Nullable String countQueryProjection) {
50 |
51 | Assert.hasText(countQuery, "CountQuery must not be empty");
52 |
53 | return DeclaredQuery.of(countQuery, false);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ExpressionBasedStringQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.regex.Pattern;
4 | import org.springframework.data.jpa.repository.query.JpaEntityMetadata;
5 | import org.springframework.expression.Expression;
6 | import org.springframework.expression.ParserContext;
7 | import org.springframework.expression.spel.standard.SpelExpressionParser;
8 | import org.springframework.expression.spel.support.StandardEvaluationContext;
9 | import org.springframework.util.Assert;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | public class ExpressionBasedStringQuery extends StringQuery {
15 |
16 | private static final String EXPRESSION_PARAMETER = "$1#{";
17 | private static final String QUOTED_EXPRESSION_PARAMETER = "$1__HASH__{";
18 |
19 | private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile("([:?])#\\{");
20 | private static final Pattern EXPRESSION_PARAMETER_UNQUOTING =
21 | Pattern.compile("([:?])__HASH__\\{");
22 |
23 | private static final String ENTITY_NAME = "entityName";
24 | private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME;
25 | private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE;
26 |
27 | public ExpressionBasedStringQuery(
28 | String query,
29 | JpaEntityMetadata> metadata,
30 | SpelExpressionParser parser,
31 | boolean nativeQuery) {
32 | super(
33 | renderQueryIfExpressionOrReturnQuery(query, metadata, parser),
34 | nativeQuery && !containsExpression(query));
35 | }
36 |
37 | static ExpressionBasedStringQuery from(
38 | DeclaredQuery query,
39 | JpaEntityMetadata> metadata,
40 | SpelExpressionParser parser,
41 | boolean nativeQuery) {
42 | return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
43 | }
44 |
45 | private static String renderQueryIfExpressionOrReturnQuery(
46 | String query, JpaEntityMetadata> metadata, SpelExpressionParser parser) {
47 |
48 | Assert.notNull(query, "query must not be null");
49 | Assert.notNull(metadata, "metadata must not be null");
50 | Assert.notNull(parser, "parser must not be null");
51 |
52 | if (!containsExpression(query)) {
53 | return query;
54 | }
55 |
56 | StandardEvaluationContext evalContext = new StandardEvaluationContext();
57 | evalContext.setVariable(ENTITY_NAME, metadata.getEntityName());
58 |
59 | query = potentiallyQuoteExpressionsParameter(query);
60 |
61 | Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);
62 |
63 | String result = expr.getValue(evalContext, String.class);
64 |
65 | if (result == null) {
66 | return query;
67 | }
68 |
69 | return potentiallyUnquoteParameterExpressions(result);
70 | }
71 |
72 | private static String potentiallyUnquoteParameterExpressions(String result) {
73 | return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER);
74 | }
75 |
76 | private static String potentiallyQuoteExpressionsParameter(String query) {
77 | return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER);
78 | }
79 |
80 | private static boolean containsExpression(String query) {
81 | return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/Jpa21Utils.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.*;
4 | import java.lang.reflect.Method;
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import org.hibernate.reactive.stage.Stage;
9 | import org.springframework.data.jpa.repository.query.JpaEntityGraph;
10 | import org.springframework.data.jpa.repository.support.MutableQueryHints;
11 | import org.springframework.data.jpa.repository.support.QueryHints;
12 | import org.springframework.lang.Nullable;
13 | import org.springframework.util.*;
14 |
15 | /**
16 | * @author Bao.Ngo
17 | */
18 | public class Jpa21Utils {
19 |
20 | private static final @Nullable Method GET_ENTITY_GRAPH_METHOD;
21 | private static final boolean JPA21_AVAILABLE =
22 | ClassUtils.isPresent(
23 | "jakarta.persistence.NamedEntityGraph",
24 | org.springframework.data.jpa.repository.query.Jpa21Utils.class.getClassLoader());
25 |
26 | static {
27 | if (JPA21_AVAILABLE) {
28 | GET_ENTITY_GRAPH_METHOD =
29 | ReflectionUtils.findMethod(EntityManager.class, "getEntityGraph", String.class);
30 | } else {
31 | GET_ENTITY_GRAPH_METHOD = null;
32 | }
33 | }
34 |
35 | private Jpa21Utils() {
36 | // prevent instantiation
37 | }
38 |
39 | public static QueryHints getFetchGraphHint(
40 | Stage.Session em, @Nullable JpaEntityGraph entityGraph, Class> entityType) {
41 | MutableQueryHints result = new MutableQueryHints();
42 |
43 | if (entityGraph == null) {
44 | return result;
45 | }
46 |
47 | EntityGraph> graph = tryGetFetchGraph(em, entityGraph, entityType);
48 | if (graph == null) {
49 | return result;
50 | }
51 |
52 | result.add(entityGraph.getType().getKey(), graph);
53 | return result;
54 | }
55 |
56 | @Nullable
57 | private static EntityGraph> tryGetFetchGraph(
58 | Stage.Session session, JpaEntityGraph jpaEntityGraph, Class> entityType) {
59 | Assert.notNull(session, "EntityManager must not be null");
60 | Assert.notNull(jpaEntityGraph, "EntityGraph must not be null");
61 | Assert.notNull(entityType, "EntityType must not be null");
62 |
63 | Assert.isTrue(
64 | JPA21_AVAILABLE,
65 | "The EntityGraph-Feature requires at least a JPA 2.1 persistence provider");
66 | Assert.isTrue(
67 | GET_ENTITY_GRAPH_METHOD != null,
68 | "It seems that you have the JPA 2.1 API but a JPA 2.0 implementation on the classpath");
69 |
70 | try {
71 | // first check whether an entityGraph with that name is already registered.
72 | return session.getEntityGraph(entityType, jpaEntityGraph.getName());
73 | } catch (Exception ex) {
74 | // try to create and dynamically register the entityGraph
75 | return createDynamicEntityGraph(session, jpaEntityGraph, entityType);
76 | }
77 | }
78 |
79 | private static EntityGraph> createDynamicEntityGraph(
80 | Stage.Session em, JpaEntityGraph jpaEntityGraph, Class> entityType) {
81 | Assert.notNull(em, "EntityManager must not be null");
82 | Assert.notNull(jpaEntityGraph, "JpaEntityGraph must not be null");
83 | Assert.notNull(entityType, "Entity type must not be null");
84 | Assert.isTrue(
85 | jpaEntityGraph.isAdHocEntityGraph(), "The given " + jpaEntityGraph + " is not dynamic");
86 |
87 | EntityGraph> entityGraph = em.createEntityGraph(entityType);
88 | configureFetchGraphFrom(jpaEntityGraph, entityGraph);
89 |
90 | return entityGraph;
91 | }
92 |
93 | static void configureFetchGraphFrom(JpaEntityGraph jpaEntityGraph, EntityGraph> entityGraph) {
94 | List attributePaths = new ArrayList<>(jpaEntityGraph.getAttributePaths());
95 |
96 | // Sort to ensure that the intermediate entity subgraphs are created accordingly.
97 | Collections.sort(attributePaths);
98 |
99 | for (String path : attributePaths) {
100 | String[] pathComponents = StringUtils.delimitedListToStringArray(path, ".");
101 | createGraph(pathComponents, 0, entityGraph, null);
102 | }
103 | }
104 |
105 | private static void createGraph(
106 | String[] pathComponents, int offset, EntityGraph> root, @Nullable Subgraph> parent) {
107 | String attributeName = pathComponents[offset];
108 |
109 | // we found our leaf property, now let's see if it already exists and add it if not
110 | if (pathComponents.length - 1 == offset) {
111 | if (parent == null && !exists(attributeName, root.getAttributeNodes())) {
112 | root.addAttributeNodes(attributeName);
113 | } else if (parent != null && !exists(attributeName, parent.getAttributeNodes())) {
114 | parent.addAttributeNodes(attributeName);
115 | }
116 |
117 | return;
118 | }
119 |
120 | AttributeNode> node = findAttributeNode(attributeName, root, parent);
121 | if (node != null) {
122 | Subgraph> subgraph = getSubgraph(node);
123 | if (subgraph == null) {
124 | subgraph =
125 | parent != null ? parent.addSubgraph(attributeName) : root.addSubgraph(attributeName);
126 | }
127 |
128 | createGraph(pathComponents, offset + 1, root, subgraph);
129 | return;
130 | }
131 |
132 | if (parent == null) {
133 | createGraph(pathComponents, offset + 1, root, root.addSubgraph(attributeName));
134 | } else {
135 | createGraph(pathComponents, offset + 1, root, parent.addSubgraph(attributeName));
136 | }
137 | }
138 |
139 | private static boolean exists(String attributeNodeName, List> nodes) {
140 | return findAttributeNode(attributeNodeName, nodes) != null;
141 | }
142 |
143 | @Nullable
144 | private static AttributeNode> findAttributeNode(
145 | String attributeNodeName, EntityGraph> entityGraph, @Nullable Subgraph> parent) {
146 | return findAttributeNode(
147 | attributeNodeName,
148 | parent != null ? parent.getAttributeNodes() : entityGraph.getAttributeNodes());
149 | }
150 |
151 | @Nullable
152 | private static AttributeNode> findAttributeNode(
153 | String attributeNodeName, List> nodes) {
154 | for (AttributeNode> node : nodes) {
155 | if (ObjectUtils.nullSafeEquals(node.getAttributeName(), attributeNodeName)) {
156 | return node;
157 | }
158 | }
159 |
160 | return null;
161 | }
162 |
163 | @Nullable
164 | private static Subgraph> getSubgraph(AttributeNode> node) {
165 | return node.getSubgraphs().isEmpty() ? null : node.getSubgraphs().values().iterator().next();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/NamedQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.EntityManager;
4 | import jakarta.persistence.EntityManagerFactory;
5 | import jakarta.persistence.Tuple;
6 | import java.util.Optional;
7 | import java.util.concurrent.locks.Lock;
8 | import java.util.concurrent.locks.ReentrantLock;
9 | import org.apache.commons.logging.Log;
10 | import org.apache.commons.logging.LogFactory;
11 | import org.hibernate.reactive.stage.Stage;
12 | import org.springframework.data.repository.query.*;
13 | import org.springframework.lang.Nullable;
14 | import reactor.core.publisher.Mono;
15 |
16 | /**
17 | * @author Bao.Ngo
18 | */
19 | public class NamedQuery extends AbstractReactiveJpaQuery {
20 |
21 | private static final String CANNOT_EXTRACT_QUERY =
22 | "Your persistence provider does not support extracting the JPQL query from a "
23 | + "named query thus you can't use Pageable inside your query method; Make sure you "
24 | + "have a JpaDialect configured at your EntityManagerFactoryBean as this affects "
25 | + "discovering the concrete persistence provider";
26 |
27 | private static final Log LOG = LogFactory.getLog(NamedQuery.class);
28 |
29 | private final EntityManagerFactory emf;
30 | private final String queryName;
31 | private final String countQueryName;
32 | private final @Nullable String countProjection;
33 | private final QueryParameterSetter.QueryMetadataCache metadataCache;
34 |
35 | private final Lock lock = new ReentrantLock();
36 |
37 | private boolean namedCountQueryIsPresent;
38 | private DeclaredQuery declaredQuery;
39 |
40 | private boolean fullyInitialized;
41 |
42 | private NamedQuery(
43 | ReactiveJpaQueryMethod method,
44 | Stage.SessionFactory sessionFactory,
45 | EntityManagerFactory emf) {
46 | super(method, sessionFactory);
47 |
48 | this.emf = emf;
49 | this.queryName = method.getNamedQueryName();
50 | this.countQueryName = method.getNamedCountQueryName();
51 | this.countProjection = method.getCountQueryProjection();
52 | this.metadataCache = new QueryParameterSetter.QueryMetadataCache();
53 |
54 | Parameters, ?> parameters = method.getParameters();
55 | if (parameters.hasSortParameter()) {
56 | throw new IllegalStateException(
57 | String.format(
58 | "Finder method %s is backed by a NamedQuery and must "
59 | + "not contain a sort parameter as we cannot modify the query; Use @Query instead",
60 | method));
61 | }
62 |
63 | if (parameters.hasPageableParameter()) {
64 | LOG.warn(
65 | String.format(
66 | "Finder method %s is backed by a NamedQuery but contains a Pageable parameter; Sorting delivered via this Pageable will not be applied",
67 | method));
68 | }
69 |
70 | this.namedCountQueryIsPresent = hasNamedQuery(emf, countQueryName);
71 | }
72 |
73 | // TODO
74 | /* static boolean hasNamedQuery(Stage.SessionFactory em, String queryName) {
75 | try {
76 | em.createNamedQuery(queryName);
77 | return true;
78 | } catch (IllegalArgumentException e) {
79 |
80 | if (LOG.isDebugEnabled()) {
81 | LOG.debug(String.format("Did not find named query %s", queryName));
82 | }
83 | return false;
84 | } finally {
85 | }
86 | }*/
87 |
88 | @Nullable
89 | public static RepositoryQuery lookupFrom(
90 | ReactiveJpaQueryMethod method,
91 | Stage.SessionFactory sessionFactory,
92 | EntityManagerFactory emf) {
93 | String queryName = method.getNamedQueryName();
94 |
95 | if (LOG.isDebugEnabled()) {
96 | LOG.debug(String.format("Looking up named query %s", queryName));
97 | }
98 |
99 | // TODO
100 | if (!hasNamedQuery(emf, queryName)) {
101 | return null;
102 | }
103 |
104 | if (method.isScrollQuery()) {
105 | throw QueryCreationException.create(
106 | method, "Scroll queries are not supported using String-based queries");
107 | }
108 |
109 | try {
110 |
111 | RepositoryQuery query = new NamedQuery(method, sessionFactory, emf);
112 | if (LOG.isDebugEnabled()) {
113 | LOG.debug(String.format("Found named query %s", queryName));
114 | }
115 | return query;
116 | } catch (IllegalArgumentException e) {
117 | return null;
118 | }
119 | }
120 |
121 | static boolean hasNamedQuery(EntityManagerFactory emf, String queryName) {
122 | try (EntityManager lookupEm = emf.createEntityManager()) {
123 | lookupEm.createNamedQuery(queryName);
124 | return true;
125 | } catch (IllegalArgumentException e) {
126 | if (LOG.isDebugEnabled()) {
127 | LOG.debug(String.format("Did not find named query %s", queryName));
128 | }
129 | return false;
130 | }
131 | }
132 |
133 | @Override
134 | protected Mono doCreateQuery(
135 | Mono session,
136 | ReactiveJpaParametersParameterAccessor accessor,
137 | ReactiveJpaQueryMethod method) {
138 | ReactiveJpaQueryMethod queryMethod = getQueryMethod();
139 | ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
140 | // Class> typeToRead = getTypeToRead(processor.getReturnedType());
141 |
142 | return session
143 | .doOnNext(this::initialize)
144 | .zipWhen(__ -> Mono.fromSupplier(() -> getTypeToRead(processor.getReturnedType())))
145 | .map(
146 | t -> {
147 | Stage.Session s = t.getT1();
148 | Optional> typeToRead = t.getT2();
149 |
150 | return typeToRead.isEmpty()
151 | ? s.createNamedQuery(queryName)
152 | : s.createNamedQuery(queryName, typeToRead.get());
153 | })
154 | .zipWhen(q -> Mono.fromSupplier(() -> metadataCache.getMetadata(queryName, q)))
155 | .flatMap(t -> parameterBinder.get().bindAndPrepare(t.getT1(), t.getT2(), accessor));
156 | // Stage.AbstractQuery query = typeToRead == null //
157 | // ? em.createNamedQuery(queryName) //
158 | // : em.createNamedQuery(queryName, typeToRead);
159 |
160 | // QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryName, query);
161 |
162 | // return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
163 | // TODO
164 | // return Mono.empty();
165 | }
166 |
167 | @Override
168 | protected Mono doCreateCountQuery(
169 | Mono session, ReactiveJpaParametersParameterAccessor accessor) {
170 | // Mono countQuery;
171 | // String cacheKey = "";
172 | return session
173 | .doOnNext(this::initialize)
174 | .flatMap(
175 | s -> {
176 | String cacheKey;
177 | Stage.AbstractQuery countQuery;
178 | if (namedCountQueryIsPresent) {
179 | cacheKey = countQueryName;
180 | countQuery = s.createNamedQuery(countQueryName, Long.class);
181 | } else {
182 | String countQueryString =
183 | declaredQuery.deriveCountQuery(null, countProjection).getQueryString();
184 | cacheKey = countQueryString;
185 | countQuery = s.createQuery(countQueryString, Long.class);
186 | }
187 |
188 | QueryParameterSetter.QueryMetadata metadata =
189 | metadataCache.getMetadata(cacheKey, countQuery);
190 |
191 | return parameterBinder.get().bind(countQuery, metadata, accessor);
192 | });
193 |
194 | // if (namedCountQueryIsPresent) {
195 | // cacheKey = countQueryName;
196 | // countQuery = session.map(s -> s.createNamedQuery(countQueryName, Long.class));
197 | // } else {
198 | // String countQueryString = declaredQuery.deriveCountQuery(null,
199 | // countProjection).getQueryString();
200 | // cacheKey = countQueryString;
201 | // countQuery = em.createQuery(countQueryString, Long.class);
202 | // }
203 |
204 | // QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(cacheKey,
205 | // countQuery);
206 | //
207 | // return parameterBinder.get().bind(countQuery, metadata, accessor);
208 | // // TODO
209 | // return null;
210 | }
211 |
212 | @Override
213 | protected Optional> getTypeToRead(ReturnedType returnedType) {
214 | if (getQueryMethod().isNativeQuery()) {
215 | Class> type = returnedType.getReturnedType();
216 | Class> domainType = returnedType.getDomainType();
217 |
218 | // Domain or subtype -> use return type
219 | if (domainType.isAssignableFrom(type)) {
220 | return Optional.of(type);
221 | }
222 |
223 | // Domain type supertype -> use domain type
224 | if (type.isAssignableFrom(domainType)) {
225 | return Optional.of(domainType);
226 | }
227 |
228 | // Tuples for projection interfaces or explicit SQL mappings for everything else
229 | return type.isInterface() ? Optional.of(Tuple.class) : Optional.empty();
230 | }
231 |
232 | return declaredQuery.hasConstructorExpression() //
233 | ? Optional.empty() //
234 | : super.getTypeToRead(returnedType);
235 | }
236 |
237 | private void initialize(Stage.Session session) {
238 | if (fullyInitialized) {
239 | return;
240 | }
241 |
242 | try {
243 | lock.lock();
244 | ReactiveJpaQueryExtractor extractor = method.getQueryExtractor();
245 | //
246 | // Parameters, ?> parameters = method.getParameters();
247 | // if (parameters.hasSortParameter()) {
248 | // throw new IllegalStateException(
249 | // String.format(
250 | // "Finder method %s is backed by a NamedQuery and must "
251 | // + "not contain a sort parameter as we cannot modify the query; Use
252 | // @Query instead",
253 | // method));
254 | // }
255 | //
256 | // this.namedCountQueryIsPresent = hasNamedQuery(emf, countQueryName);
257 |
258 | Stage.Query query = session.createNamedQuery(queryName);
259 | String queryString = extractor.extractQueryString(query);
260 | this.declaredQuery = DeclaredQuery.of(queryString, false);
261 |
262 | boolean needToCreateCountQuery =
263 | !namedCountQueryIsPresent && method.getParameters().hasLimitingParameters();
264 |
265 | if (needToCreateCountQuery && !extractor.canExtractQuery()) {
266 | throw QueryCreationException.create(method, CANNOT_EXTRACT_QUERY);
267 | }
268 |
269 | fullyInitialized = true;
270 | } finally {
271 | lock.unlock();
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/NativeReactiveJpaQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.Tuple;
4 | import org.hibernate.reactive.stage.Stage;
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.domain.Sort;
7 | import org.springframework.data.jpa.repository.QueryRewriter;
8 | import org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException;
9 | import org.springframework.data.repository.query.Parameters;
10 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
11 | import org.springframework.data.repository.query.ReturnedType;
12 | import org.springframework.expression.spel.standard.SpelExpressionParser;
13 | import org.springframework.lang.Nullable;
14 | import reactor.core.publisher.Mono;
15 |
16 | /**
17 | * @author Bao.Ngo
18 | */
19 | public class NativeReactiveJpaQuery extends AbstractStringBasedReactiveJpaQuery {
20 |
21 | public NativeReactiveJpaQuery(
22 | ReactiveJpaQueryMethod method,
23 | Stage.SessionFactory sessionFactory,
24 | String queryString,
25 | @Nullable String countQueryString,
26 | QueryRewriter rewriter,
27 | ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
28 | SpelExpressionParser parser) {
29 |
30 | super(
31 | method,
32 | sessionFactory,
33 | queryString,
34 | countQueryString,
35 | rewriter,
36 | evaluationContextProvider,
37 | parser);
38 |
39 | Parameters, ?> parameters = method.getParameters();
40 |
41 | if (parameters.hasSortParameter() && !queryString.contains("#sort")) {
42 | throw new InvalidJpaQueryMethodException(
43 | "Cannot use native queries with dynamic sorting in method " + method);
44 | }
45 | }
46 |
47 | @Override
48 | protected Mono createReactiveJpaQuery(
49 | Mono session,
50 | String queryString,
51 | ReactiveJpaQueryMethod method,
52 | Sort sort,
53 | Pageable pageable,
54 | ReturnedType returnedType) {
55 | return session.map(
56 | s -> {
57 | Class> type = getTypeToQueryFor(returnedType);
58 | return type == null
59 | ? s.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable))
60 | : s.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable), type);
61 | });
62 | // Class> type = getTypeToQueryFor(returnedType);
63 | //
64 | // return type == null
65 | // ? session.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable))
66 | // : session.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable),
67 | // type);
68 | }
69 |
70 | @Nullable
71 | private Class> getTypeToQueryFor(ReturnedType returnedType) {
72 | Class> result = getQueryMethod().isQueryForEntity() ? returnedType.getDomainType() : null;
73 |
74 | if (this.getQuery().hasConstructorExpression() || this.getQuery().isDefaultProjection()) {
75 | return result;
76 | }
77 |
78 | return returnedType.isProjecting()
79 | && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) //
80 | ? Tuple.class
81 | : result;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ParameterBinder.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.hibernate.reactive.stage.Stage;
4 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
5 | import org.springframework.data.jpa.support.PageableUtils;
6 | import org.springframework.util.Assert;
7 | import reactor.core.publisher.Flux;
8 | import reactor.core.publisher.Mono;
9 |
10 | /**
11 | * @author Bao.Ngo
12 | */
13 | public class ParameterBinder {
14 |
15 | static final String PARAMETER_NEEDS_TO_BE_NAMED =
16 | "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters";
17 |
18 | private final ReactiveJpaParameters parameters;
19 | private final Iterable parameterSetters;
20 | private final boolean useJpaForPaging;
21 |
22 | ParameterBinder(
23 | ReactiveJpaParameters parameters, Iterable parameterSetters) {
24 | this(parameters, parameterSetters, true);
25 | }
26 |
27 | public ParameterBinder(
28 | ReactiveJpaParameters parameters,
29 | Iterable parameterSetters,
30 | boolean useJpaForPaging) {
31 |
32 | Assert.notNull(parameters, "ReactiveJpaParameters must not be null");
33 | Assert.notNull(parameterSetters, "Parameter setters must not be null");
34 |
35 | this.parameters = parameters;
36 | this.parameterSetters = parameterSetters;
37 | this.useJpaForPaging = useJpaForPaging;
38 | }
39 |
40 | public Mono bind(
41 | T jpaQuery,
42 | QueryParameterSetter.QueryMetadata metadata,
43 | JpaParametersParameterAccessor accessor) {
44 |
45 | return bind(metadata.withQuery(jpaQuery), accessor, QueryParameterSetter.ErrorHandling.STRICT)
46 | .thenReturn(jpaQuery);
47 | // return jpaQuery;
48 | }
49 |
50 | public Mono bind(
51 | QueryParameterSetter.BindableQuery query,
52 | JpaParametersParameterAccessor accessor,
53 | QueryParameterSetter.ErrorHandling errorHandling) {
54 |
55 | // for (QueryParameterSetter setter : parameterSetters) {
56 | // setter.setParameter(query, accessor, errorHandling);
57 | // }
58 | return Flux.fromIterable(parameterSetters)
59 | .concatMap(setter -> setter.setParameter(query, accessor, errorHandling))
60 | .then();
61 | }
62 |
63 | Mono bindAndPrepare(
64 | Stage.AbstractQuery query,
65 | QueryParameterSetter.QueryMetadata metadata,
66 | JpaParametersParameterAccessor accessor) {
67 |
68 | return bind(query, metadata, accessor)
69 | .then(
70 | Mono.defer(
71 | () -> {
72 | if (!useJpaForPaging
73 | || !parameters.hasLimitingParameters()
74 | || accessor.getPageable().isUnpaged()) {
75 | return Mono.just(query);
76 | }
77 |
78 | if (query instanceof Stage.SelectionQuery> selectionQuery) {
79 | selectionQuery.setFirstResult(
80 | PageableUtils.getOffsetAsInteger(accessor.getPageable()));
81 | selectionQuery.setMaxResults(accessor.getPageable().getPageSize());
82 | }
83 |
84 | return Mono.just(query);
85 | }));
86 |
87 | // if (!useJpaForPaging
88 | // || !parameters.hasLimitingParameters()
89 | // || accessor.getPageable().isUnpaged()) {
90 | // return query;
91 | // }
92 |
93 | // if (query instanceof Stage.SelectionQuery> selectionQuery) {
94 | // selectionQuery.setFirstResult(PageableUtils.getOffsetAsInteger(accessor.getPageable()));
95 | // selectionQuery.setMaxResults(accessor.getPageable().getPageSize());
96 | // }
97 | //
98 | // return query;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ParameterBinderFactory.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
6 | import org.springframework.expression.spel.standard.SpelExpressionParser;
7 | import org.springframework.util.Assert;
8 |
9 | public class ParameterBinderFactory {
10 |
11 | static ParameterBinder createBinder(ReactiveJpaParameters parameters) {
12 | Assert.notNull(parameters, "ReactiveJpaParameters must not be null");
13 |
14 | QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.basic(parameters);
15 | List bindings = getBindings(parameters);
16 |
17 | return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
18 | }
19 |
20 | static ParameterBinder createCriteriaBinder(
21 | ReactiveJpaParameters parameters,
22 | List> metadata) {
23 |
24 | Assert.notNull(parameters, "ReactiveJpaParameters must not be null");
25 | Assert.notNull(metadata, "Parameter metadata must not be null");
26 |
27 | QueryParameterSetterFactory setterFactory =
28 | QueryParameterSetterFactory.forCriteriaQuery(parameters, metadata);
29 | List bindings = getBindings(parameters);
30 |
31 | return new ParameterBinder(parameters, createSetters(bindings, setterFactory));
32 | }
33 |
34 | static ParameterBinder createQueryAwareBinder(
35 | ReactiveJpaParameters parameters,
36 | DeclaredQuery query,
37 | SpelExpressionParser parser,
38 | ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
39 |
40 | Assert.notNull(parameters, "ReactiveJpaParameters must not be null");
41 | Assert.notNull(query, "StringQuery must not be null");
42 | Assert.notNull(parser, "SpelExpressionParser must not be null");
43 | Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
44 |
45 | List bindings = query.getParameterBindings();
46 | QueryParameterSetterFactory expressionSetterFactory =
47 | QueryParameterSetterFactory.parsing(parser, evaluationContextProvider, parameters);
48 |
49 | QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters);
50 |
51 | return new ParameterBinder(
52 | parameters,
53 | createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
54 | !query.usesPaging());
55 | }
56 |
57 | private static List getBindings(ReactiveJpaParameters parameters) {
58 |
59 | List result = new ArrayList<>();
60 | int bindableParameterIndex = 0;
61 |
62 | for (ReactiveJpaParameters.JpaParameter parameter : parameters) {
63 |
64 | if (parameter.isBindable()) {
65 | int index = ++bindableParameterIndex;
66 | ParameterBinding.BindingIdentifier bindingIdentifier =
67 | parameter
68 | .getName()
69 | .map(it -> ParameterBinding.BindingIdentifier.of(it, index))
70 | .orElseGet(() -> ParameterBinding.BindingIdentifier.of(index));
71 |
72 | result.add(
73 | new ParameterBinding(
74 | bindingIdentifier,
75 | ParameterBinding.ParameterOrigin.ofParameter(bindingIdentifier)));
76 | }
77 | }
78 |
79 | return result;
80 | }
81 |
82 | private static Iterable createSetters(
83 | List parameterBindings, QueryParameterSetterFactory... factories) {
84 | return createSetters(parameterBindings, EmptyDeclaredQuery.EMPTY_QUERY, factories);
85 | }
86 |
87 | private static Iterable createSetters(
88 | List parameterBindings,
89 | DeclaredQuery declaredQuery,
90 | QueryParameterSetterFactory... strategies) {
91 | List setters = new ArrayList<>(parameterBindings.size());
92 | for (ParameterBinding parameterBinding : parameterBindings) {
93 | setters.add(createQueryParameterSetter(parameterBinding, strategies, declaredQuery));
94 | }
95 |
96 | return setters;
97 | }
98 |
99 | private static QueryParameterSetter createQueryParameterSetter(
100 | ParameterBinding binding,
101 | QueryParameterSetterFactory[] strategies,
102 | DeclaredQuery declaredQuery) {
103 | for (QueryParameterSetterFactory strategy : strategies) {
104 | QueryParameterSetter setter = strategy.create(binding, declaredQuery);
105 |
106 | if (setter != null) {
107 | return setter;
108 | }
109 | }
110 |
111 | return QueryParameterSetter.NOOP;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ParameterMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.criteria.CriteriaBuilder;
4 | import jakarta.persistence.criteria.ParameterExpression;
5 | import java.util.*;
6 | import java.util.function.Supplier;
7 | import java.util.stream.Collectors;
8 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
9 | import org.springframework.data.repository.query.Parameter;
10 | import org.springframework.data.repository.query.Parameters;
11 | import org.springframework.data.repository.query.ParametersParameterAccessor;
12 | import org.springframework.data.repository.query.parser.Part;
13 | import org.springframework.expression.Expression;
14 | import org.springframework.lang.Nullable;
15 | import org.springframework.util.Assert;
16 | import org.springframework.util.ClassUtils;
17 | import org.springframework.util.CollectionUtils;
18 | import org.springframework.util.ObjectUtils;
19 |
20 | public class ParameterMetadataProvider {
21 |
22 | private final CriteriaBuilder builder;
23 | private final Iterator extends Parameter> parameters;
24 | private final List> expressions;
25 | private final @Nullable Iterator bindableParameterValues;
26 | private final EscapeCharacter escape;
27 |
28 | public ParameterMetadataProvider(
29 | CriteriaBuilder builder, ParametersParameterAccessor accessor, EscapeCharacter escape) {
30 | this(builder, accessor.iterator(), accessor.getParameters(), escape);
31 | }
32 |
33 | public ParameterMetadataProvider(
34 | CriteriaBuilder builder, Parameters, ?> parameters, EscapeCharacter escape) {
35 | this(builder, null, parameters, escape);
36 | }
37 |
38 | private ParameterMetadataProvider(
39 | CriteriaBuilder builder,
40 | @Nullable Iterator bindableParameterValues,
41 | Parameters, ?> parameters,
42 | EscapeCharacter escape) {
43 |
44 | Assert.notNull(builder, "CriteriaBuilder must not be null");
45 | Assert.notNull(parameters, "Parameters must not be null");
46 | Assert.notNull(escape, "EscapeCharacter must not be null");
47 |
48 | this.builder = builder;
49 | this.parameters = parameters.getBindableParameters().iterator();
50 | this.expressions = new ArrayList<>();
51 | this.bindableParameterValues = bindableParameterValues;
52 | this.escape = escape;
53 | }
54 |
55 | public List> getExpressions() {
56 | return expressions;
57 | }
58 |
59 | @SuppressWarnings("unchecked")
60 | public ParameterMetadataProvider.ParameterMetadata next(Part part) {
61 | Assert.isTrue(
62 | parameters.hasNext(), () -> String.format("No parameter available for part %s", part));
63 |
64 | Parameter parameter = parameters.next();
65 | return (ParameterMetadataProvider.ParameterMetadata)
66 | next(part, parameter.getType(), parameter);
67 | }
68 |
69 | @SuppressWarnings("unchecked")
70 | public ParameterMetadataProvider.ParameterMetadata extends T> next(
71 | Part part, Class type) {
72 | Parameter parameter = parameters.next();
73 | Class> typeToUse =
74 | ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type;
75 | return (ParameterMetadataProvider.ParameterMetadata extends T>)
76 | next(part, typeToUse, parameter);
77 | }
78 |
79 | private ParameterMetadataProvider.ParameterMetadata next(
80 | Part part, Class type, Parameter parameter) {
81 | Assert.notNull(type, "Type must not be null");
82 | /*
83 | * We treat Expression types as Object vales since the real value to be bound as a parameter is determined at query time.
84 | */
85 | @SuppressWarnings("unchecked")
86 | Class reifiedType = Expression.class.equals(type) ? (Class) Object.class : type;
87 |
88 | Supplier name =
89 | () ->
90 | parameter
91 | .getName()
92 | .orElseThrow(() -> new IllegalArgumentException("o_O Parameter needs to be named"));
93 |
94 | ParameterExpression expression =
95 | parameter.isExplicitlyNamed() //
96 | ? builder.parameter(reifiedType, name.get()) //
97 | : builder.parameter(reifiedType);
98 |
99 | Object value =
100 | bindableParameterValues == null
101 | ? ParameterMetadataProvider.ParameterMetadata.PLACEHOLDER
102 | : bindableParameterValues.next();
103 |
104 | ParameterMetadataProvider.ParameterMetadata metadata =
105 | new ParameterMetadataProvider.ParameterMetadata<>(expression, part, value, escape);
106 | expressions.add(metadata);
107 |
108 | return metadata;
109 | }
110 |
111 | EscapeCharacter getEscape() {
112 | return escape;
113 | }
114 |
115 | static class ParameterMetadata {
116 |
117 | static final Object PLACEHOLDER = new Object();
118 |
119 | private final Part.Type type;
120 | private final ParameterExpression expression;
121 | private final EscapeCharacter escape;
122 | private final boolean ignoreCase;
123 | private final boolean noWildcards;
124 |
125 | public ParameterMetadata(
126 | ParameterExpression expression,
127 | Part part,
128 | @Nullable Object value,
129 | EscapeCharacter escape) {
130 |
131 | this.expression = expression;
132 | this.type =
133 | value == null && Part.Type.SIMPLE_PROPERTY.equals(part.getType())
134 | ? Part.Type.IS_NULL
135 | : part.getType();
136 | this.ignoreCase = Part.IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
137 | this.noWildcards = part.getProperty().getLeafProperty().isCollection();
138 | this.escape = escape;
139 | }
140 |
141 | public ParameterExpression getExpression() {
142 | return expression;
143 | }
144 |
145 | public boolean isIsNullParameter() {
146 | return Part.Type.IS_NULL.equals(type);
147 | }
148 |
149 | @Nullable
150 | public Object prepare(@Nullable Object value) {
151 | if (value == null || expression.getJavaType() == null) {
152 | return value;
153 | }
154 |
155 | if (String.class.equals(expression.getJavaType()) && !noWildcards) {
156 | switch (type) {
157 | case STARTING_WITH:
158 | return String.format("%s%%", escape.escape(value.toString()));
159 | case ENDING_WITH:
160 | return String.format("%%%s", escape.escape(value.toString()));
161 | case CONTAINING:
162 | case NOT_CONTAINING:
163 | return String.format("%%%s%%", escape.escape(value.toString()));
164 | default:
165 | return value;
166 | }
167 | }
168 |
169 | return Collection.class.isAssignableFrom(expression.getJavaType()) //
170 | ? upperIfIgnoreCase(ignoreCase, toCollection(value)) //
171 | : value;
172 | }
173 |
174 | @Nullable
175 | private static Collection> toCollection(@Nullable Object value) {
176 | if (value == null) {
177 | return null;
178 | }
179 |
180 | if (value instanceof Collection> collection) {
181 | return collection.isEmpty() ? null : collection;
182 | }
183 |
184 | if (ObjectUtils.isArray(value)) {
185 | List collection = Arrays.asList(ObjectUtils.toObjectArray(value));
186 | return collection.isEmpty() ? null : collection;
187 | }
188 |
189 | return Collections.singleton(value);
190 | }
191 |
192 | @Nullable
193 | @SuppressWarnings("unchecked")
194 | private static Collection> upperIfIgnoreCase(
195 | boolean ignoreCase, @Nullable Collection> collection) {
196 | if (!ignoreCase || CollectionUtils.isEmpty(collection)) {
197 | return collection;
198 | }
199 |
200 | return ((Collection) collection)
201 | .stream() //
202 | .map(
203 | it ->
204 | it == null //
205 | ? null //
206 | : it.toUpperCase()) //
207 | .collect(Collectors.toList());
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ParameterValueEvaluator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Optional;
4 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
5 | import reactor.core.publisher.Mono;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | public interface ParameterValueEvaluator {
11 |
12 | Mono> evaluate(JpaParametersParameterAccessor accessor);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/PredicateBuilder.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import static com.htech.data.jpa.reactive.repository.query.QueryUtils.toExpressionRecursively;
4 | import static org.springframework.data.repository.query.parser.Part.Type.*;
5 |
6 | import jakarta.persistence.criteria.*;
7 | import java.util.Collection;
8 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
9 | import org.springframework.data.mapping.PropertyPath;
10 | import org.springframework.data.repository.query.parser.Part;
11 | import org.springframework.util.Assert;
12 |
13 | @SuppressWarnings({"unchecked", "rawtypes"})
14 | public class PredicateBuilder {
15 |
16 | private final ParameterMetadataProvider provider;
17 | private final CriteriaBuilder builder;
18 | private final Part part;
19 | private final Root> root;
20 | private final EscapeCharacter escape;
21 |
22 | public PredicateBuilder(
23 | ParameterMetadataProvider provider,
24 | CriteriaBuilder builder,
25 | Part part,
26 | Root> root,
27 | EscapeCharacter escape) {
28 | this.provider = provider;
29 | this.builder = builder;
30 | this.part = part;
31 | this.root = root;
32 | this.escape = escape;
33 | }
34 |
35 | public Predicate build() {
36 | PropertyPath property = part.getProperty();
37 | Part.Type type = part.getType();
38 |
39 | switch (type) {
40 | case BETWEEN:
41 | ParameterMetadataProvider.ParameterMetadata first = provider.next(part);
42 | ParameterMetadataProvider.ParameterMetadata second = provider.next(part);
43 | return builder.between(
44 | getComparablePath(root, part), first.getExpression(), second.getExpression());
45 | case AFTER:
46 | case GREATER_THAN:
47 | return builder.greaterThan(
48 | getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
49 | case GREATER_THAN_EQUAL:
50 | return builder.greaterThanOrEqualTo(
51 | getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
52 | case BEFORE:
53 | case LESS_THAN:
54 | return builder.lessThan(
55 | getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
56 | case LESS_THAN_EQUAL:
57 | return builder.lessThanOrEqualTo(
58 | getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
59 | case IS_NULL:
60 | return getTypedPath(root, part).isNull();
61 | case IS_NOT_NULL:
62 | return getTypedPath(root, part).isNotNull();
63 | case NOT_IN:
64 | // cast required for eclipselink workaround, see DATAJPA-433
65 | return upperIfIgnoreCase(getTypedPath(root, part))
66 | .in((Expression>) provider.next(part, Collection.class).getExpression())
67 | .not();
68 | case IN:
69 | // cast required for eclipselink workaround, see DATAJPA-433
70 | return upperIfIgnoreCase(getTypedPath(root, part))
71 | .in((Expression>) provider.next(part, Collection.class).getExpression());
72 | case STARTING_WITH:
73 | case ENDING_WITH:
74 | case CONTAINING:
75 | case NOT_CONTAINING:
76 | if (property.getLeafProperty().isCollection()) {
77 |
78 | Expression> propertyExpression = traversePath(root, property);
79 | ParameterExpression parameterExpression = provider.next(part).getExpression();
80 |
81 | // Can't just call .not() in case of negation as EclipseLink chokes on that.
82 | return type.equals(NOT_CONTAINING) //
83 | ? isNotMember(builder, parameterExpression, propertyExpression) //
84 | : isMember(builder, parameterExpression, propertyExpression);
85 | }
86 |
87 | case LIKE:
88 | case NOT_LIKE:
89 | Expression stringPath = getTypedPath(root, part);
90 | Expression propertyExpression = upperIfIgnoreCase(stringPath);
91 | Expression parameterExpression =
92 | upperIfIgnoreCase(provider.next(part, String.class).getExpression());
93 | Predicate like =
94 | builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
95 | return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like;
96 | case TRUE:
97 | Expression truePath = getTypedPath(root, part);
98 | return builder.isTrue(truePath);
99 | case FALSE:
100 | Expression falsePath = getTypedPath(root, part);
101 | return builder.isFalse(falsePath);
102 | case SIMPLE_PROPERTY:
103 | ParameterMetadataProvider.ParameterMetadata expression = provider.next(part);
104 | Expression path = getTypedPath(root, part);
105 | return expression.isIsNullParameter()
106 | ? path.isNull()
107 | : builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
108 | case NEGATING_SIMPLE_PROPERTY:
109 | return builder.notEqual(
110 | upperIfIgnoreCase(getTypedPath(root, part)),
111 | upperIfIgnoreCase(provider.next(part).getExpression()));
112 | case IS_EMPTY:
113 | case IS_NOT_EMPTY:
114 | if (!property.getLeafProperty().isCollection()) {
115 | throw new IllegalArgumentException(
116 | "IsEmpty / IsNotEmpty can only be used on collection properties");
117 | }
118 |
119 | Expression> collectionPath = traversePath(root, property);
120 | return type.equals(IS_NOT_EMPTY)
121 | ? builder.isNotEmpty(collectionPath)
122 | : builder.isEmpty(collectionPath);
123 |
124 | default:
125 | throw new IllegalArgumentException("Unsupported keyword " + type);
126 | }
127 | }
128 |
129 | private Predicate isMember(
130 | CriteriaBuilder builder, Expression parameter, Expression> property) {
131 | return builder.isMember(parameter, property);
132 | }
133 |
134 | private Predicate isNotMember(
135 | CriteriaBuilder builder, Expression parameter, Expression> property) {
136 | return builder.isNotMember(parameter, property);
137 | }
138 |
139 | private Expression upperIfIgnoreCase(Expression extends T> expression) {
140 | switch (part.shouldIgnoreCase()) {
141 | case ALWAYS:
142 | Assert.state(
143 | canUpperCase(expression),
144 | "Unable to ignore case of "
145 | + expression.getJavaType().getName()
146 | + " types, the property '"
147 | + part.getProperty().getSegment()
148 | + "' must reference a String");
149 | return (Expression) builder.upper((Expression) expression);
150 |
151 | case WHEN_POSSIBLE:
152 | if (canUpperCase(expression)) {
153 | return (Expression) builder.upper((Expression) expression);
154 | }
155 |
156 | case NEVER:
157 | default:
158 | return (Expression) expression;
159 | }
160 | }
161 |
162 | private boolean canUpperCase(Expression> expression) {
163 | return String.class.equals(expression.getJavaType());
164 | }
165 |
166 | private Expression extends Comparable> getComparablePath(Root> root, Part part) {
167 | return getTypedPath(root, part);
168 | }
169 |
170 | private Expression getTypedPath(Root> root, Part part) {
171 | return toExpressionRecursively(root, part.getProperty());
172 | }
173 |
174 | private Expression traversePath(Path> root, PropertyPath path) {
175 | Path result = root.get(path.getSegment());
176 | return (Expression) (path.hasNext() ? traversePath(result, path.next()) : result);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ProcedureParameter.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.ParameterMode;
4 | import java.util.Objects;
5 | import org.springframework.lang.Nullable;
6 |
7 | public class ProcedureParameter {
8 |
9 | private final String name;
10 | private final ParameterMode mode;
11 | private final Class> type;
12 |
13 | ProcedureParameter(@Nullable String name, ParameterMode mode, Class> type) {
14 |
15 | this.name = name;
16 | this.mode = mode;
17 | this.type = type;
18 | }
19 |
20 | public String getName() {
21 | return name;
22 | }
23 |
24 | public ParameterMode getMode() {
25 | return mode;
26 | }
27 |
28 | public Class> getType() {
29 | return type;
30 | }
31 |
32 | @Override
33 | public boolean equals(Object o) {
34 |
35 | if (this == o) {
36 | return true;
37 | }
38 |
39 | if (!(o instanceof ProcedureParameter)) {
40 | return false;
41 | }
42 |
43 | ProcedureParameter that = (ProcedureParameter) o;
44 | return Objects.equals(name, that.name) && mode == that.mode && Objects.equals(type, that.type);
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(name, mode, type);
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return "ProcedureParameter{"
55 | + "name='"
56 | + name
57 | + '\''
58 | + ", mode="
59 | + mode
60 | + ", type="
61 | + type
62 | + '}';
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/QueryEnhancer.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Set;
4 | import org.springframework.data.domain.Sort;
5 | import org.springframework.data.jpa.repository.query.QueryUtils;
6 | import org.springframework.lang.Nullable;
7 |
8 | /**
9 | * @author Bao.Ngo
10 | */
11 | public interface QueryEnhancer {
12 |
13 | default String applySorting(Sort sort) {
14 | return applySorting(sort, detectAlias());
15 | }
16 |
17 | String applySorting(Sort sort, @Nullable String alias);
18 |
19 | @Nullable
20 | String detectAlias();
21 |
22 | default String createCountQueryFor() {
23 | return createCountQueryFor(null);
24 | }
25 |
26 | String createCountQueryFor(@Nullable String countProjection);
27 |
28 | default boolean hasConstructorExpression() {
29 | return QueryUtils.hasConstructorExpression(getQuery().getQueryString());
30 | }
31 |
32 | String getProjection();
33 |
34 | Set getJoinAliases();
35 |
36 | DeclaredQuery getQuery();
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/QueryEnhancerFactory.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.springframework.util.ClassUtils;
6 |
7 | public class QueryEnhancerFactory {
8 |
9 | private static final Log LOG = LogFactory.getLog(QueryEnhancerFactory.class);
10 |
11 | private static final boolean jSqlParserPresent =
12 | ClassUtils.isPresent(
13 | "net.sf.jsqlparser.parser.JSqlParser",
14 | org.springframework.data.jpa.repository.query.QueryEnhancerFactory.class
15 | .getClassLoader());
16 |
17 | static {
18 | if (jSqlParserPresent) {
19 | LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used");
20 | }
21 | }
22 |
23 | private QueryEnhancerFactory() {}
24 |
25 | public static QueryEnhancer forQuery(DeclaredQuery query) {
26 | // TODO
27 | if (query.isNativeQuery()) {
28 | return new DefaultQueryEnhancer(query);
29 | }
30 |
31 | try {
32 | return ReactiveJpaQueryEnhancer.forHql(query);
33 | } catch (Exception e) {
34 | throw new RuntimeException(e.getMessage(), e);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/QueryParameterSetter.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.Parameter;
4 | import jakarta.persistence.Query;
5 | import jakarta.persistence.TemporalType;
6 | import jakarta.persistence.criteria.ParameterExpression;
7 | import java.lang.reflect.Proxy;
8 | import java.util.*;
9 | import org.apache.commons.logging.Log;
10 | import org.apache.commons.logging.LogFactory;
11 | import org.hibernate.reactive.stage.Stage;
12 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
13 | import org.springframework.data.util.NullableWrapperConverters;
14 | import org.springframework.lang.Nullable;
15 | import org.springframework.util.Assert;
16 | import reactor.core.publisher.Mono;
17 |
18 | /**
19 | * @author Bao.Ngo
20 | */
21 | public interface QueryParameterSetter {
22 |
23 | Mono setParameter(
24 | QueryParameterSetter.BindableQuery query,
25 | JpaParametersParameterAccessor accessor,
26 | QueryParameterSetter.ErrorHandling errorHandling);
27 |
28 | QueryParameterSetter NOOP = (query, values, errorHandling) -> Mono.empty();
29 |
30 | class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter {
31 |
32 | // protected final boolean useIndexBasedParams;
33 | // protected final Function valueExtractor;
34 | protected final ParameterValueEvaluator valueEvaluator;
35 | protected final ParameterBinding binding;
36 | protected final Parameter> parameter;
37 | protected final @Nullable TemporalType temporalType;
38 |
39 | NamedOrIndexedQueryParameterSetter(
40 | // Function valueExtractor,
41 | ParameterValueEvaluator valueEvaluator,
42 | ParameterBinding binding,
43 | Parameter> parameter,
44 | @Nullable TemporalType temporalType) {
45 | Assert.notNull(valueEvaluator, "ValueEvaluator must not be null");
46 |
47 | this.valueEvaluator = valueEvaluator;
48 | this.binding = binding;
49 | this.parameter = parameter;
50 | this.temporalType = temporalType;
51 | }
52 |
53 | @SuppressWarnings("unchecked")
54 | @Override
55 | public Mono setParameter(
56 | BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling) {
57 | if (temporalType != null) {
58 | return Mono.empty();
59 | // TODO
60 | } else {
61 |
62 | return valueEvaluator
63 | .evaluate(accessor)
64 | .map(binding::prepare)
65 | .doOnNext(
66 | value -> {
67 | Object unwrapped;
68 | if (NullableWrapperConverters.supports(value.getClass())) {
69 | unwrapped = NullableWrapperConverters.unwrap(value);
70 | } else {
71 | unwrapped = value;
72 | }
73 |
74 | if (parameter instanceof ParameterExpression) {
75 | errorHandling.execute(
76 | () -> query.setParameter((Parameter) parameter, unwrapped));
77 | } else if (parameter.getName() != null) {
78 | errorHandling.execute(() -> query.setParameter(parameter.getName(), unwrapped));
79 |
80 | } else {
81 | Integer position = parameter.getPosition();
82 |
83 | if (position != null) {
84 | errorHandling.execute(() -> query.setParameter(position, unwrapped));
85 | }
86 | }
87 | })
88 | .then();
89 |
90 | // if (parameter instanceof ParameterExpression) {
91 | // return errorHandling.execute(
92 | // () -> query.setParameter((Parameter) parameter, value));
93 | // } else if (parameter.getName() != null) {
94 | // return errorHandling.execute(() -> query.setParameter(parameter.getName(),
95 | // value));
96 | //
97 | // } else {
98 | // Integer position = parameter.getPosition();
99 | //
100 | // if (position != null) {
101 | // return errorHandling.execute(() -> query.setParameter(position, value));
102 | // }
103 | // }
104 |
105 | // return Mono.error(
106 | // () -> new IllegalStateException("Illegal parameter " + parameter.getClass()));
107 | }
108 | }
109 | }
110 |
111 | enum ErrorHandling {
112 | STRICT {
113 |
114 | @Override
115 | public void execute(Runnable block) {
116 | block.run();
117 | }
118 | },
119 |
120 | LENIENT {
121 |
122 | @Override
123 | public void execute(Runnable block) {
124 | // return Mono.fromRunnable(block)
125 | // .onErrorResume(
126 | // RuntimeException.class,
127 | // rex -> {
128 | // LOG.info("Silently ignoring", rex);
129 | // return Mono.empty();
130 | // })
131 | // .then();
132 | try {
133 | block.run();
134 | } catch (RuntimeException rex) {
135 | LOG.info("Silently ignoring", rex);
136 | }
137 | }
138 | };
139 |
140 | private static final Log LOG = LogFactory.getLog(QueryParameterSetter.ErrorHandling.class);
141 |
142 | abstract void execute(Runnable block);
143 | }
144 |
145 | class QueryMetadataCache {
146 |
147 | private Map cache = Collections.emptyMap();
148 |
149 | public QueryParameterSetter.QueryMetadata getMetadata(
150 | String cacheKey, Stage.AbstractQuery query) {
151 | QueryParameterSetter.QueryMetadata queryMetadata = cache.get(cacheKey);
152 |
153 | if (queryMetadata == null) {
154 | queryMetadata = new QueryParameterSetter.QueryMetadata(query);
155 |
156 | Map cache;
157 |
158 | if (this.cache.isEmpty()) {
159 | cache = Collections.singletonMap(cacheKey, queryMetadata);
160 | } else {
161 | cache = new HashMap<>(this.cache);
162 | cache.put(cacheKey, queryMetadata);
163 | }
164 |
165 | synchronized (this) {
166 | this.cache = cache;
167 | }
168 | }
169 |
170 | return queryMetadata;
171 | }
172 | }
173 |
174 | class QueryMetadata {
175 |
176 | private final boolean namedParameters = false;
177 | private final Set> parameters = new HashSet<>();
178 | private final boolean registerExcessParameters = false;
179 |
180 | QueryMetadata(Stage.AbstractQuery query) {
181 |
182 | /* this.namedParameters = QueryUtils.hasNamedParameter(query);
183 | this.parameters = query.getParameters();
184 |
185 | // DATAJPA-1172
186 | // Since EclipseLink doesn't reliably report whether a query has parameters
187 | // we simply try to set the parameters and ignore possible failures.
188 | // this is relevant for native queries with SpEL expressions, where the method parameters don't have to match the
189 | // parameters in the query.
190 | // https://bugs.eclipse.org/bugs/show_bug.cgi?id=521915
191 |
192 | this.registerExcessParameters = query.getParameters().size() == 0
193 | && unwrapClass(query).getName().startsWith("org.eclipse");*/
194 | }
195 |
196 | QueryMetadata(QueryParameterSetter.QueryMetadata metadata) {
197 |
198 | // this.namedParameters = metadata.namedParameters;
199 | // this.parameters = metadata.parameters;
200 | // this.registerExcessParameters = metadata.registerExcessParameters;
201 | }
202 |
203 | public QueryParameterSetter.BindableQuery withQuery(Stage.AbstractQuery query) {
204 | return new QueryParameterSetter.BindableQuery(this, query);
205 | }
206 |
207 | public Set> getParameters() {
208 | return parameters;
209 | }
210 |
211 | public boolean hasNamedParameters() {
212 | return this.namedParameters;
213 | }
214 |
215 | public boolean registerExcessParameters() {
216 | return this.registerExcessParameters;
217 | }
218 |
219 | private static Class> unwrapClass(Query query) {
220 |
221 | Class extends Query> queryType = query.getClass();
222 |
223 | try {
224 |
225 | return Proxy.isProxyClass(queryType) //
226 | ? query.unwrap(null).getClass() //
227 | : queryType;
228 |
229 | } catch (RuntimeException e) {
230 |
231 | LogFactory.getLog(QueryParameterSetter.QueryMetadata.class)
232 | .warn("Failed to unwrap actual class for Query proxy", e);
233 |
234 | return queryType;
235 | }
236 | }
237 | }
238 |
239 | class BindableQuery extends QueryMetadata {
240 |
241 | protected final Stage.AbstractQuery query;
242 |
243 | // protected final Stage.AbstractQuery unwrapped;
244 |
245 | BindableQuery(QueryParameterSetter.QueryMetadata metadata, Stage.AbstractQuery query) {
246 | super(metadata);
247 | this.query = query;
248 | // this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
249 | }
250 |
251 | private BindableQuery(Stage.AbstractQuery query) {
252 | super(query);
253 | this.query = query;
254 | // this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
255 | }
256 |
257 | public static QueryParameterSetter.BindableQuery from(Stage.AbstractQuery query) {
258 | return new QueryParameterSetter.BindableQuery(query);
259 | }
260 |
261 | public Stage.AbstractQuery getQuery() {
262 | return query;
263 | }
264 |
265 | public Stage.AbstractQuery setParameter(Parameter param, T value) {
266 | return query.setParameter(param, value);
267 | }
268 |
269 | public Stage.AbstractQuery setParameter(
270 | Parameter param, Date value, TemporalType temporalType) {
271 | return query.setParameter(param, value /*, temporalType*/);
272 | }
273 |
274 | public Stage.AbstractQuery setParameter(String name, Object value) {
275 | return query.setParameter(name, value);
276 | }
277 |
278 | public Stage.AbstractQuery setParameter(String name, Date value, TemporalType temporalType) {
279 | return query.setParameter(name, value /*, temporalType*/);
280 | }
281 |
282 | public Stage.AbstractQuery setParameter(int position, Object value) {
283 | return query.setParameter(position, value);
284 | }
285 |
286 | public Stage.AbstractQuery setParameter(int position, Date value, TemporalType temporalType) {
287 | return query.setParameter(position, value /*, temporalType*/);
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaCountQueryCreator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.criteria.*;
4 | import org.springframework.data.domain.Sort;
5 | import org.springframework.data.repository.query.ReturnedType;
6 | import org.springframework.data.repository.query.parser.PartTree;
7 | import org.springframework.lang.Nullable;
8 |
9 | /**
10 | * @author Bao.Ngo
11 | */
12 | public class ReactiveJpaCountQueryCreator extends ReactiveJpaCriteriaQueryCreator {
13 |
14 | protected final boolean distinct;
15 |
16 | public ReactiveJpaCountQueryCreator(
17 | PartTree tree,
18 | ReturnedType type,
19 | CriteriaBuilder builder,
20 | ParameterMetadataProvider provider) {
21 | super(tree, type, builder, provider);
22 | distinct = tree.isDistinct();
23 | }
24 |
25 | @Override
26 | protected CriteriaQuery extends Object> createCriteriaQuery(
27 | CriteriaBuilder builder, ReturnedType type) {
28 | return builder.createQuery(Long.class);
29 | }
30 |
31 | @Override
32 | @SuppressWarnings("unchecked")
33 | protected CriteriaQuery extends Object> complete(
34 | @Nullable Predicate predicate,
35 | Sort sort,
36 | CriteriaQuery extends Object> query,
37 | CriteriaBuilder builder,
38 | Root> root) {
39 |
40 | CriteriaQuery extends Object> select = query.select(getCountQuery(query, builder, root));
41 | return predicate == null ? select : select.where(predicate);
42 | }
43 |
44 | @SuppressWarnings("rawtypes")
45 | private Expression getCountQuery(CriteriaQuery> query, CriteriaBuilder builder, Root> root) {
46 | return distinct ? builder.countDistinct(root) : builder.count(root);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaCriteriaDeleteQueryCreator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import static org.springframework.data.repository.query.parser.Part.Type.*;
4 |
5 | import jakarta.persistence.criteria.*;
6 | import java.util.Collection;
7 | import java.util.Iterator;
8 | import java.util.List;
9 | import org.springframework.data.domain.Sort;
10 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
11 | import org.springframework.data.repository.query.ReturnedType;
12 | import org.springframework.data.repository.query.parser.Part;
13 | import org.springframework.data.repository.query.parser.PartTree;
14 | import org.springframework.lang.Nullable;
15 |
16 | /**
17 | * @author Bao.Ngo
18 | */
19 | public class ReactiveJpaCriteriaDeleteQueryCreator
20 | extends AbstractQueryCreator, Predicate> {
21 |
22 | protected final CriteriaBuilder builder;
23 | protected final Root> root;
24 | protected final CriteriaDelete> query;
25 | protected final ParameterMetadataProvider provider;
26 | protected final ReturnedType returnedType;
27 | protected final PartTree tree;
28 | protected final EscapeCharacter escape;
29 |
30 | public ReactiveJpaCriteriaDeleteQueryCreator(
31 | PartTree tree,
32 | ReturnedType type,
33 | CriteriaBuilder builder,
34 | ParameterMetadataProvider provider) {
35 |
36 | super(tree);
37 | this.tree = tree;
38 |
39 | CriteriaDelete> criteriaDelete = createCriteriaQuery(builder, type);
40 |
41 | this.builder = builder;
42 | this.query = criteriaDelete;
43 | this.root = query.from((Class) type.getDomainType());
44 | this.provider = provider;
45 | this.returnedType = type;
46 | this.escape = provider.getEscape();
47 | }
48 |
49 | protected CriteriaDelete extends Object> createCriteriaQuery(
50 | CriteriaBuilder builder, ReturnedType type) {
51 | Class> typeToRead = type.getDomainType();
52 |
53 | return builder.createCriteriaDelete(typeToRead);
54 | }
55 |
56 | public List> getParameterExpressions() {
57 | return provider.getExpressions();
58 | }
59 |
60 | @Override
61 | protected Predicate create(Part part, Iterator iterator) {
62 | return toPredicate(part, root);
63 | }
64 |
65 | @Override
66 | protected Predicate and(Part part, Predicate base, Iterator iterator) {
67 | return builder.and(base, toPredicate(part, root));
68 | }
69 |
70 | @Override
71 | protected Predicate or(Predicate base, Predicate predicate) {
72 | return builder.or(base, predicate);
73 | }
74 |
75 | @Override
76 | protected final CriteriaDelete> complete(Predicate predicate, Sort sort) {
77 | return complete(predicate, sort, query, builder, root);
78 | }
79 |
80 | @SuppressWarnings({"unchecked", "rawtypes"})
81 | protected CriteriaDelete> complete(
82 | @Nullable Predicate predicate,
83 | Sort sort,
84 | CriteriaDelete> query,
85 | CriteriaBuilder builder,
86 | Root> root) {
87 |
88 | CriteriaDelete> delete = query;
89 | return predicate == null ? delete : delete.where(predicate);
90 | }
91 |
92 | Collection getRequiredSelection(Sort sort, ReturnedType returnedType) {
93 | return returnedType.getInputProperties();
94 | }
95 |
96 | private Predicate toPredicate(Part part, Root> root) {
97 | return new PredicateBuilder(provider, builder, part, root, escape).build();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaCriteriaQueryCreator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import static com.htech.data.jpa.reactive.repository.query.QueryUtils.toExpressionRecursively;
4 | import static org.springframework.data.repository.query.parser.Part.Type.*;
5 |
6 | import jakarta.persistence.criteria.*;
7 | import jakarta.persistence.metamodel.SingularAttribute;
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.Iterator;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 | import org.springframework.data.domain.Sort;
14 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
15 | import org.springframework.data.jpa.repository.query.QueryUtils;
16 | import org.springframework.data.mapping.PropertyPath;
17 | import org.springframework.data.repository.query.ReturnedType;
18 | import org.springframework.data.repository.query.parser.Part;
19 | import org.springframework.data.repository.query.parser.PartTree;
20 | import org.springframework.lang.Nullable;
21 |
22 | /**
23 | * @author Bao.Ngo
24 | */
25 | public class ReactiveJpaCriteriaQueryCreator
26 | extends AbstractQueryCreator, Predicate> {
27 |
28 | protected final CriteriaBuilder builder;
29 | protected final Root> root;
30 | protected final CriteriaQuery extends Object> query;
31 | protected final ParameterMetadataProvider provider;
32 | protected final ReturnedType returnedType;
33 | protected final PartTree tree;
34 | protected final EscapeCharacter escape;
35 |
36 | public ReactiveJpaCriteriaQueryCreator(
37 | PartTree tree,
38 | ReturnedType type,
39 | CriteriaBuilder builder,
40 | ParameterMetadataProvider provider) {
41 | super(tree);
42 | this.tree = tree;
43 |
44 | CriteriaQuery> criteriaQuery = createCriteriaQuery(builder, type);
45 |
46 | this.builder = builder;
47 | this.query = criteriaQuery.distinct(tree.isDistinct() && !tree.isCountProjection());
48 | this.root = query.from(type.getDomainType());
49 | this.provider = provider;
50 | this.returnedType = type;
51 | this.escape = provider.getEscape();
52 | }
53 |
54 | protected CriteriaQuery extends Object> createCriteriaQuery(
55 | CriteriaBuilder builder, ReturnedType type) {
56 | Class> typeToRead = tree.isDelete() ? type.getDomainType() : type.getTypeToRead();
57 |
58 | return (typeToRead == null) || tree.isExistsProjection() //
59 | ? builder.createTupleQuery() //
60 | : builder.createQuery(typeToRead);
61 | }
62 |
63 | public List> getParameterExpressions() {
64 | return provider.getExpressions();
65 | }
66 |
67 | @Override
68 | protected Predicate create(Part part, Iterator iterator) {
69 | return toPredicate(part, root);
70 | }
71 |
72 | @Override
73 | protected Predicate and(Part part, Predicate base, Iterator iterator) {
74 | return builder.and(base, toPredicate(part, root));
75 | }
76 |
77 | @Override
78 | protected Predicate or(Predicate base, Predicate predicate) {
79 | return builder.or(base, predicate);
80 | }
81 |
82 | @Override
83 | protected final CriteriaQuery extends Object> complete(Predicate predicate, Sort sort) {
84 | return complete(predicate, sort, query, builder, root);
85 | }
86 |
87 | @SuppressWarnings({"unchecked", "rawtypes"})
88 | protected CriteriaQuery extends Object> complete(
89 | @Nullable Predicate predicate,
90 | Sort sort,
91 | CriteriaQuery extends Object> query,
92 | CriteriaBuilder builder,
93 | Root> root) {
94 | if (returnedType.needsCustomConstruction()) {
95 | Collection requiredSelection = getRequiredSelection(sort, returnedType);
96 | List> selections = new ArrayList<>();
97 |
98 | for (String property : requiredSelection) {
99 | PropertyPath path = PropertyPath.from(property, returnedType.getDomainType());
100 | selections.add(toExpressionRecursively(root, path, true).alias(property));
101 | }
102 |
103 | Class> typeToRead = returnedType.getReturnedType();
104 | query =
105 | typeToRead.isInterface() //
106 | ? query.multiselect(selections) //
107 | : query.select(
108 | (Selection)
109 | builder.construct(
110 | typeToRead, //
111 | selections.toArray(new Selection[0])));
112 |
113 | } else if (tree.isExistsProjection()) {
114 | if (root.getModel().hasSingleIdAttribute()) {
115 | SingularAttribute, ?> id =
116 | root.getModel().getId(root.getModel().getIdType().getJavaType());
117 | query = query.multiselect(root.get((SingularAttribute) id).alias(id.getName()));
118 |
119 | } else {
120 | query =
121 | query.multiselect(
122 | root.getModel().getIdClassAttributes().stream() //
123 | .map(it -> (Selection>) root.get((SingularAttribute) it).alias(it.getName()))
124 | .collect(Collectors.toList()));
125 | }
126 |
127 | } else {
128 | query = query.select((Root) root);
129 | }
130 |
131 | CriteriaQuery extends Object> select =
132 | query.orderBy(QueryUtils.toOrders(sort, root, builder));
133 |
134 | return predicate == null ? select : select.where(predicate);
135 | }
136 |
137 | Collection getRequiredSelection(Sort sort, ReturnedType returnedType) {
138 | return returnedType.getInputProperties();
139 | }
140 |
141 | private Predicate toPredicate(Part part, Root> root) {
142 | return new PredicateBuilder(provider, builder, part, root, escape).build();
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaParameters.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.TemporalType;
4 | import java.lang.reflect.Method;
5 | import java.util.Date;
6 | import java.util.List;
7 | import org.springframework.core.MethodParameter;
8 | import org.springframework.data.jpa.repository.Temporal;
9 | import org.springframework.data.repository.query.Parameter;
10 | import org.springframework.data.repository.query.Parameters;
11 | import org.springframework.data.util.TypeInformation;
12 | import org.springframework.lang.Nullable;
13 |
14 | public class ReactiveJpaParameters
15 | extends Parameters {
16 |
17 | public ReactiveJpaParameters(Method method) {
18 | super(method, null);
19 | }
20 |
21 | private ReactiveJpaParameters(List parameters) {
22 | super(parameters);
23 | }
24 |
25 | @Override
26 | protected ReactiveJpaParameters.JpaParameter createParameter(MethodParameter parameter) {
27 | return new ReactiveJpaParameters.JpaParameter(parameter);
28 | }
29 |
30 | @Override
31 | protected ReactiveJpaParameters createFrom(List parameters) {
32 | return new ReactiveJpaParameters(parameters);
33 | }
34 |
35 | public boolean hasLimitingParameters() {
36 | return hasLimitParameter() || hasPageableParameter();
37 | }
38 |
39 | public static class JpaParameter extends Parameter {
40 |
41 | private final @Nullable Temporal annotation;
42 | private @Nullable TemporalType temporalType;
43 |
44 | protected JpaParameter(MethodParameter parameter) {
45 | super(parameter, TypeInformation.of(Parameter.class));
46 |
47 | this.annotation = parameter.getParameterAnnotation(Temporal.class);
48 | this.temporalType = null;
49 |
50 | if (!isDateParameter() && hasTemporalParamAnnotation()) {
51 | throw new IllegalArgumentException(
52 | Temporal.class.getSimpleName() + " annotation is only allowed on Date parameter");
53 | }
54 | }
55 |
56 | @Override
57 | public boolean isBindable() {
58 | return super.isBindable() || isTemporalParameter();
59 | }
60 |
61 | boolean isTemporalParameter() {
62 | return isDateParameter() && hasTemporalParamAnnotation();
63 | }
64 |
65 | @Nullable
66 | TemporalType getTemporalType() {
67 | if (temporalType == null) {
68 | this.temporalType = annotation == null ? null : annotation.value();
69 | }
70 |
71 | return this.temporalType;
72 | }
73 |
74 | TemporalType getRequiredTemporalType() throws IllegalStateException {
75 | TemporalType temporalType = getTemporalType();
76 |
77 | if (temporalType != null) {
78 | return temporalType;
79 | }
80 |
81 | throw new IllegalStateException(
82 | String.format("Required temporal type not found for %s", getType()));
83 | }
84 |
85 | private boolean hasTemporalParamAnnotation() {
86 | return annotation != null;
87 | }
88 |
89 | private boolean isDateParameter() {
90 | return getType().equals(Date.class);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaParametersParameterAccessor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Map;
6 | import java.util.Optional;
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import org.hibernate.reactive.stage.Stage;
9 | import org.reactivestreams.Publisher;
10 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
11 | import org.springframework.data.repository.query.Parameters;
12 | import org.springframework.data.repository.util.ReactiveWrapperConverters;
13 | import org.springframework.data.util.ReactiveWrappers;
14 | import reactor.core.publisher.Flux;
15 | import reactor.core.publisher.Mono;
16 |
17 | /**
18 | * @author Bao.Ngo
19 | */
20 | public class ReactiveJpaParametersParameterAccessor extends JpaParametersParameterAccessor {
21 |
22 | protected final ReactiveJpaQueryMethod method;
23 | protected final Object[] values;
24 | protected final Stage.SessionFactory sessionFactory;
25 |
26 | public ReactiveJpaParametersParameterAccessor(
27 | ReactiveJpaQueryMethod method,
28 | Parameters, ?> parameters,
29 | Object[] values,
30 | Stage.SessionFactory sessionFactory /*,
31 | Mono session,
32 | Stage.Session session,
33 | Stage.Transaction transaction*/) {
34 | super(parameters, values);
35 | this.method = method;
36 | this.values = values;
37 | this.sessionFactory = sessionFactory;
38 | }
39 |
40 | public Stage.SessionFactory getSessionFactory() {
41 | return sessionFactory;
42 | }
43 |
44 | // public Mono getSession() {
45 | // return session;
46 | // }
47 |
48 | // public Stage.Transaction getTransaction() {
49 | // return transaction;
50 | // }
51 | @SuppressWarnings("unchecked")
52 | public Mono resolveParameters() {
53 |
54 | boolean hasReactiveWrapper = false;
55 |
56 | for (Object value : values) {
57 | if (value == null || !ReactiveWrappers.supports(value.getClass())) {
58 | continue;
59 | }
60 |
61 | hasReactiveWrapper = true;
62 | break;
63 | }
64 |
65 | if (!hasReactiveWrapper) {
66 | return Mono.just(this);
67 | }
68 |
69 | Object[] resolved = new Object[values.length];
70 | Map> holder = new ConcurrentHashMap<>();
71 | List> publishers = new ArrayList<>();
72 |
73 | for (int i = 0; i < values.length; i++) {
74 |
75 | Object value = resolved[i] = values[i];
76 | if (value == null || !ReactiveWrappers.supports(value.getClass())) {
77 | continue;
78 | }
79 |
80 | if (ReactiveWrappers.isSingleValueType(value.getClass())) {
81 |
82 | int index = i;
83 | publishers.add(
84 | ReactiveWrapperConverters.toWrapper(value, Mono.class) //
85 | .map(Optional::of) //
86 | .defaultIfEmpty(Optional.empty()) //
87 | .doOnNext(it -> holder.put(index, (Optional>) it)));
88 | } else {
89 |
90 | int index = i;
91 | publishers.add(
92 | ReactiveWrapperConverters.toWrapper(value, Flux.class) //
93 | .collectList() //
94 | .doOnNext(it -> holder.put(index, Optional.of(it))));
95 | }
96 | }
97 |
98 | return Flux.merge(publishers)
99 | .then()
100 | .thenReturn(resolved)
101 | .map(
102 | values -> {
103 | holder.forEach((index, v) -> values[index] = v.orElse(null));
104 | return new ReactiveJpaParametersParameterAccessor(
105 | method, getParameters(), values, sessionFactory);
106 | });
107 | }
108 |
109 | public Parameters, ?> getBindableParameters() {
110 | return getParameters().getBindableParameters();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaQueryEnhancer.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Method;
5 | import java.util.Set;
6 | import org.springframework.data.domain.Sort;
7 | import org.springframework.lang.Nullable;
8 | import org.springframework.util.Assert;
9 | import org.springframework.util.ReflectionUtils;
10 |
11 | /**
12 | * @author Bao.Ngo
13 | */
14 | public class ReactiveJpaQueryEnhancer implements QueryEnhancer {
15 |
16 | protected final DeclaredQuery query;
17 | protected final Object queryParser;
18 |
19 | private ReactiveJpaQueryEnhancer(DeclaredQuery query, Object queryParser) {
20 | this.query = query;
21 | this.queryParser = queryParser;
22 | }
23 |
24 | public static ReactiveJpaQueryEnhancer forJpql(DeclaredQuery query) throws Exception {
25 | Assert.notNull(query, "DeclaredQuery must not be null!");
26 |
27 | Class> clazz = Class.forName("org.springframework.data.jpa.repository.query.JpqlQueryParser");
28 | Constructor> constructor = ReflectionUtils.accessibleConstructor(clazz, String.class);
29 | return new ReactiveJpaQueryEnhancer(query, constructor.newInstance(query.getQueryString()));
30 | }
31 |
32 | public static ReactiveJpaQueryEnhancer forHql(DeclaredQuery query) throws Exception {
33 | Assert.notNull(query, "DeclaredQuery must not be null!");
34 |
35 | Class> clazz = Class.forName("org.springframework.data.jpa.repository.query.HqlQueryParser");
36 | Constructor> constructor = ReflectionUtils.accessibleConstructor(clazz, String.class);
37 | return new ReactiveJpaQueryEnhancer(query, constructor.newInstance(query.getQueryString()));
38 | }
39 |
40 | /*public static ReactiveJpaQueryEnhancer forEql(DeclaredQuery query) {
41 |
42 | Assert.notNull(query, "DeclaredQuery must not be null!");
43 |
44 | return new ReactiveJpaQueryEnhancer(query, new EqlQueryParser(query.getQueryString()));
45 | }*/
46 |
47 | protected Object getQueryParsingStrategy() {
48 | return queryParser;
49 | }
50 |
51 | @Override
52 | public String applySorting(Sort sort) {
53 | return invokeQueryParser("renderSortedQuery", new Class[] {Sort.class}, sort);
54 | }
55 |
56 | private T invokeQueryParser(
57 | String methodName, Class>[] methodParamTypes, Object... params) {
58 | try {
59 | Method method;
60 | if (methodParamTypes == null || methodParamTypes.length == 0) {
61 | method = ReflectionUtils.findMethod(queryParser.getClass(), methodName);
62 | } else {
63 | method = ReflectionUtils.findMethod(queryParser.getClass(), methodName, methodParamTypes);
64 | }
65 | ReflectionUtils.makeAccessible(method);
66 |
67 | if (params == null || params.length == 0) {
68 | return (T) method.invoke(queryParser);
69 | }
70 |
71 | return (T) method.invoke(queryParser, params);
72 | } catch (Exception e) {
73 | throw new RuntimeException(e.getMessage(), e);
74 | }
75 | }
76 |
77 | @Override
78 | public String applySorting(Sort sort, String alias) {
79 | return applySorting(sort);
80 | }
81 |
82 | @Override
83 | public String detectAlias() {
84 | return invokeQueryParser("findAlias", null);
85 | // try {
86 | // Method method = ReflectionUtils.findMethod(queryParser.getClass(), "findAlias");
87 | // ReflectionUtils.makeAccessible(method);
88 | // return (String) method.invoke(queryParser);
89 | // } catch (Exception e) {
90 | // throw new RuntimeException(e.getMessage(), e);
91 | // }
92 | }
93 |
94 | @Override
95 | public String createCountQueryFor() {
96 | return createCountQueryFor(null);
97 | }
98 |
99 | @Override
100 | public String createCountQueryFor(@Nullable String countProjection) {
101 | return invokeQueryParser("createCountQuery", new Class[] {String.class}, countProjection);
102 | }
103 |
104 | @Override
105 | public boolean hasConstructorExpression() {
106 | return invokeQueryParser("hasConstructorExpression", null);
107 | }
108 |
109 | @Override
110 | public String getProjection() {
111 | return invokeQueryParser("projection", null);
112 | }
113 |
114 | @Override
115 | public Set getJoinAliases() {
116 | return Set.of();
117 | }
118 |
119 | @Override
120 | public DeclaredQuery getQuery() {
121 | return query;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaQueryExecutionConverters.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import io.smallrye.mutiny.Multi;
4 | import io.smallrye.mutiny.Uni;
5 | import java.util.*;
6 | import java.util.function.Function;
7 | import java.util.stream.Stream;
8 | import org.springframework.core.convert.TypeDescriptor;
9 | import org.springframework.core.convert.converter.GenericConverter;
10 | import org.springframework.core.convert.support.GenericConversionService;
11 | import org.springframework.data.repository.util.QueryExecutionConverters;
12 | import org.springframework.data.util.NullableWrapperConverters;
13 | import org.springframework.data.util.TypeInformation;
14 | import org.springframework.lang.Nullable;
15 | import org.springframework.util.Assert;
16 | import org.springframework.util.ConcurrentReferenceHashMap;
17 | import org.springframework.util.ObjectUtils;
18 | import reactor.core.publisher.Flux;
19 | import reactor.core.publisher.Mono;
20 |
21 | /**
22 | * @author Bao.Ngo
23 | */
24 | public class ReactiveJpaQueryExecutionConverters {
25 |
26 | private static final Set WRAPPER_TYPES = new HashSet<>();
27 | private static final Set UN_WRAPPER_TYPES = new HashSet<>();
28 | private static final Set> UNWRAPPERS = new HashSet<>();
29 | private static final Set> ALLOWED_PAGEABLE_TYPES = new HashSet<>();
30 | private static final Map, QueryExecutionConverters.ExecutionAdapter> EXECUTION_ADAPTER =
31 | new HashMap<>();
32 | private static final Map, Boolean> supportsCache = new ConcurrentReferenceHashMap<>();
33 | private static final TypeInformation VOID_INFORMATION = TypeInformation.of(Void.class);
34 |
35 | private static final GenericConversionService CONVERSION_SERVICE = new GenericConversionService();
36 |
37 | static {
38 | CONVERSION_SERVICE.addConverter(new ReactiveFlatteningConverter());
39 | WRAPPER_TYPES.add(WrapperType.singleValue(Mono.class));
40 | WRAPPER_TYPES.add(WrapperType.singleValue(Uni.class));
41 | //
42 | // WRAPPER_TYPES.add(QueryExecutionConverters.WrapperType.singleValue(CompletableFuture.class));
43 |
44 | UN_WRAPPER_TYPES.add(WrapperType.multiValue(Flux.class));
45 | UN_WRAPPER_TYPES.add(WrapperType.multiValue(Uni.class));
46 | //
47 | // UNWRAPPER_TYPES.add(QueryExecutionConverters.WrapperType.singleValue(CompletableFuture.class));
48 |
49 | // ALLOWED_PAGEABLE_TYPES.add(Slice.class);
50 | // ALLOWED_PAGEABLE_TYPES.add(Page.class);
51 | // ALLOWED_PAGEABLE_TYPES.add(List.class);
52 | // ALLOWED_PAGEABLE_TYPES.add(Window.class);
53 |
54 | //
55 | // WRAPPER_TYPES.add(QueryExecutionConverters.NullableWrapperToCompletableFutureConverter.getWrapperType());
56 |
57 | // UNWRAPPERS.addAll(CustomCollections.getUnwrappers());
58 |
59 | //
60 | // CustomCollections.getCustomTypes().stream().map(QueryExecutionConverters.WrapperType::multiValue).forEach(WRAPPER_TYPES::add);
61 |
62 | // ALLOWED_PAGEABLE_TYPES.addAll(CustomCollections.getPaginationReturnTypes());
63 |
64 | }
65 |
66 | private ReactiveJpaQueryExecutionConverters() {}
67 |
68 | public static boolean supports(Class> type) {
69 |
70 | Assert.notNull(type, "Type must not be null");
71 |
72 | return supportsCache.computeIfAbsent(
73 | type,
74 | key -> {
75 | for (WrapperType candidate : WRAPPER_TYPES) {
76 | if (candidate.getType().isAssignableFrom(key)) {
77 | return true;
78 | }
79 | }
80 |
81 | return NullableWrapperConverters.supports(type);
82 | });
83 | }
84 |
85 | public static boolean isSingleValue(Class> type) {
86 | // if (NullableWrapperConverters.supports(type)) {
87 | // return NullableWrapperConverters.isSingleValue(type);
88 | // }
89 |
90 | for (WrapperType candidate : WRAPPER_TYPES) {
91 | if (candidate.getType().isAssignableFrom(type)) {
92 | return candidate.isSingleValue();
93 | }
94 | }
95 |
96 | return false;
97 | }
98 |
99 | public static GenericConversionService getDefaultConversionService() {
100 | return CONVERSION_SERVICE;
101 | }
102 |
103 | public static final class WrapperType {
104 |
105 | private WrapperType(Class> type, WrapperType.Cardinality cardinality) {
106 | this.type = type;
107 | this.cardinality = cardinality;
108 | }
109 |
110 | public Class> getType() {
111 | return this.type;
112 | }
113 |
114 | public WrapperType.Cardinality getCardinality() {
115 | return cardinality;
116 | }
117 |
118 | @Override
119 | public boolean equals(@Nullable Object o) {
120 |
121 | if (this == o) {
122 | return true;
123 | }
124 |
125 | if (!(o instanceof WrapperType that)) {
126 | return false;
127 | }
128 |
129 | if (!ObjectUtils.nullSafeEquals(type, that.type)) {
130 | return false;
131 | }
132 |
133 | return cardinality == that.cardinality;
134 | }
135 |
136 | @Override
137 | public int hashCode() {
138 | int result = ObjectUtils.nullSafeHashCode(type);
139 | result = 31 * result + ObjectUtils.nullSafeHashCode(cardinality);
140 | return result;
141 | }
142 |
143 | @Override
144 | public String toString() {
145 | return "QueryExecutionConverters.WrapperType(type="
146 | + this.getType()
147 | + ", cardinality="
148 | + this.getCardinality()
149 | + ")";
150 | }
151 |
152 | enum Cardinality {
153 | NONE,
154 | SINGLE,
155 | MULTI;
156 | }
157 |
158 | private final Class> type;
159 | private final WrapperType.Cardinality cardinality;
160 |
161 | public static WrapperType singleValue(Class> type) {
162 | return new WrapperType(type, WrapperType.Cardinality.SINGLE);
163 | }
164 |
165 | public static WrapperType multiValue(Class> type) {
166 | return new WrapperType(type, WrapperType.Cardinality.MULTI);
167 | }
168 |
169 | public static WrapperType noValue(Class> type) {
170 | return new WrapperType(type, WrapperType.Cardinality.NONE);
171 | }
172 |
173 | boolean isSingleValue() {
174 | return cardinality.equals(WrapperType.Cardinality.SINGLE);
175 | }
176 | }
177 |
178 | static class ReactiveFlatteningConverter implements GenericConverter {
179 |
180 | @Override
181 | public Set getConvertibleTypes() {
182 | return Set.of(
183 | /*new ConvertiblePair(Mono.class, Mono.class), */ new ConvertiblePair(
184 | Flux.class, Flux.class),
185 | /*new ConvertiblePair(Uni.class, Uni.class), */ new ConvertiblePair(
186 | Multi.class, Multi.class));
187 | }
188 |
189 | @Override
190 | public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
191 | if (source instanceof Flux> flux) {
192 | return flux.flatMap(
193 | o -> {
194 | if (o instanceof Iterable> i) {
195 | return Flux.fromIterable(i);
196 | }
197 | if (o instanceof Stream> stream) {
198 | return Flux.fromStream(stream);
199 | }
200 |
201 | return Mono.just(o);
202 | });
203 | }
204 |
205 | if (source instanceof Multi> multi) {
206 | return multi.flatMap(
207 | o -> {
208 | if (o instanceof Iterable> i) {
209 | return Multi.createFrom().iterable(i);
210 | }
211 | if (o instanceof Stream> stream) {
212 | return Multi.createFrom().items(stream);
213 | }
214 |
215 | return Multi.createFrom().items(o);
216 | });
217 | }
218 |
219 | return source;
220 | }
221 | }
222 |
223 | // static class UniFlatteningConverter extends ReactiveFlatteningConverter {
224 | //
225 | // }
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaQueryExtractor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.hibernate.reactive.stage.Stage;
4 |
5 | public interface ReactiveJpaQueryExtractor {
6 | String extractQueryString(Stage.AbstractQuery query);
7 |
8 | boolean canExtractQuery();
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaQueryFactory.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.hibernate.reactive.stage.Stage;
4 | import org.springframework.data.jpa.repository.QueryRewriter;
5 | import org.springframework.data.repository.query.QueryCreationException;
6 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
7 | import org.springframework.expression.spel.standard.SpelExpressionParser;
8 | import org.springframework.lang.Nullable;
9 |
10 | public enum ReactiveJpaQueryFactory {
11 | INSTANCE;
12 |
13 | private static final SpelExpressionParser PARSER = new SpelExpressionParser();
14 |
15 | AbstractReactiveJpaQuery fromMethodWithQueryString(
16 | ReactiveJpaQueryMethod method,
17 | Stage.SessionFactory sessionFactory,
18 | String queryString,
19 | @Nullable String countQueryString,
20 | QueryRewriter queryRewriter,
21 | ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
22 |
23 | if (method.isScrollQuery()) {
24 | throw QueryCreationException.create(
25 | method, "Scroll queries are not supported using String-based queries");
26 | }
27 |
28 | return method.isNativeQuery()
29 | ? new NativeReactiveJpaQuery(
30 | method,
31 | sessionFactory,
32 | queryString,
33 | countQueryString,
34 | queryRewriter,
35 | evaluationContextProvider,
36 | PARSER)
37 | : new SimpleReactiveJpaQuery(
38 | method,
39 | sessionFactory,
40 | queryString,
41 | countQueryString,
42 | queryRewriter,
43 | evaluationContextProvider,
44 | PARSER);
45 | }
46 |
47 | // TODO
48 | /* public StoredProcedureJpaQuery fromProcedureAnnotation(JpaQueryMethod method, EntityManager em) {
49 |
50 | if (method.isScrollQuery()) {
51 | throw QueryCreationException.create(method, "Scroll queries are not supported using stored procedures");
52 | }
53 |
54 | return new StoredProcedureJpaQuery(method, em);
55 | }*/
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveJpaQueryMethodFactory.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.lang.reflect.Method;
4 | import org.springframework.data.jpa.repository.query.JpaQueryMethod;
5 | import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
6 | import org.springframework.data.projection.ProjectionFactory;
7 | import org.springframework.data.repository.core.RepositoryMetadata;
8 |
9 | /**
10 | * @author Bao.Ngo
11 | */
12 | public class ReactiveJpaQueryMethodFactory implements JpaQueryMethodFactory {
13 |
14 | private final ReactiveJpaQueryExtractor extractor;
15 |
16 | public ReactiveJpaQueryMethodFactory(ReactiveJpaQueryExtractor extractor) {
17 | // Assert.notNull(extractor, "QueryExtractor must not be null");
18 | this.extractor = extractor;
19 | }
20 |
21 | public ReactiveJpaQueryMethod build0(
22 | Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
23 | return new ReactiveJpaQueryMethod(method, metadata, factory, extractor);
24 | }
25 |
26 | @Override
27 | public JpaQueryMethod build(
28 | Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
29 | throw new UnsupportedOperationException();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/ReactiveQueryRewriterProvider.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.springframework.beans.BeanUtils;
4 | import org.springframework.data.jpa.repository.QueryRewriter;
5 |
6 | public interface ReactiveQueryRewriterProvider {
7 |
8 | static ReactiveQueryRewriterProvider simple() {
9 |
10 | return method -> {
11 | Class extends QueryRewriter> queryRewriter = method.getQueryRewriter();
12 |
13 | if (queryRewriter == QueryRewriter.IdentityQueryRewriter.class) {
14 | return QueryRewriter.IdentityQueryRewriter.INSTANCE;
15 | }
16 |
17 | return BeanUtils.instantiateClass(queryRewriter);
18 | };
19 | }
20 |
21 | QueryRewriter getQueryRewriter(ReactiveJpaQueryMethod method);
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/SimpleReactiveJpaQuery.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import org.hibernate.reactive.stage.Stage;
4 | import org.springframework.data.jpa.repository.QueryRewriter;
5 | import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
6 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
7 | import org.springframework.expression.spel.standard.SpelExpressionParser;
8 | import org.springframework.lang.Nullable;
9 |
10 | public class SimpleReactiveJpaQuery extends AbstractStringBasedReactiveJpaQuery {
11 |
12 | public SimpleReactiveJpaQuery(
13 | ReactiveJpaQueryMethod method,
14 | Stage.SessionFactory sessionFactory,
15 | @Nullable String countQueryString,
16 | QueryRewriter queryRewriter,
17 | ReactiveExtensionAwareQueryMethodEvaluationContextProvider evaluationContextProvider,
18 | SpelExpressionParser parser) {
19 | this(
20 | method,
21 | sessionFactory,
22 | method.getRequiredAnnotatedQuery(),
23 | countQueryString,
24 | queryRewriter,
25 | evaluationContextProvider,
26 | parser);
27 | }
28 |
29 | public SimpleReactiveJpaQuery(
30 | ReactiveJpaQueryMethod method,
31 | Stage.SessionFactory sessionFactory,
32 | String queryString,
33 | @Nullable String countQueryString,
34 | QueryRewriter queryRewriter,
35 | ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
36 | SpelExpressionParser parser) {
37 |
38 | super(
39 | method,
40 | sessionFactory,
41 | queryString,
42 | countQueryString,
43 | queryRewriter,
44 | evaluationContextProvider,
45 | parser);
46 |
47 | // TODO
48 | // validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s",
49 | // method);
50 |
51 | /*if (method.isPageQuery()) {
52 | validateQuery(getCountQuery().getQueryString(),
53 | String.format("Count query validation failed for method %s", method));
54 | }*/
55 | }
56 |
57 | private void validateQuery(String query, String errorMessage, Object... arguments) {
58 |
59 | if (getQueryMethod().isProcedureQuery()) {
60 | return;
61 | }
62 |
63 | try {
64 |
65 | } catch (RuntimeException e) {
66 | // Needed as there's ambiguities in how an invalid query string shall be expressed by the
67 | // persistence provider
68 | // https://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17
69 | throw new IllegalArgumentException(String.format(errorMessage, arguments), e);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/SpELParameterValueEvaluator.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Optional;
4 | import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
5 | import org.springframework.data.repository.query.Parameters;
6 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
7 | import org.springframework.expression.Expression;
8 | import reactor.core.publisher.Mono;
9 |
10 | /**
11 | * @author Bao.Ngo
12 | */
13 | public class SpELParameterValueEvaluator implements ParameterValueEvaluator {
14 |
15 | private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
16 | private final Parameters, ?> parameters;
17 | private final Expression expression;
18 |
19 | public SpELParameterValueEvaluator(
20 | ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
21 | Parameters, ?> parameters,
22 | Expression expression) {
23 | this.evaluationContextProvider = evaluationContextProvider;
24 | this.parameters = parameters;
25 | this.expression = expression;
26 | }
27 |
28 | @Override
29 | public Mono> evaluate(JpaParametersParameterAccessor accessor) {
30 | return evaluationContextProvider
31 | .getEvaluationContextLater(parameters, accessor.getValues())
32 | .map(context -> Optional.ofNullable(expression.getValue(context, Object.class)));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/StoredProcedureAttributeSource.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import jakarta.persistence.NamedStoredProcedureQueries;
4 | import jakarta.persistence.NamedStoredProcedureQuery;
5 | import jakarta.persistence.ParameterMode;
6 | import jakarta.persistence.StoredProcedureParameter;
7 | import java.lang.reflect.Method;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 | import org.springframework.core.annotation.AnnotatedElementUtils;
14 | import org.springframework.data.jpa.repository.query.JpaEntityMetadata;
15 | import org.springframework.data.jpa.repository.query.Procedure;
16 | import org.springframework.lang.Nullable;
17 | import org.springframework.util.Assert;
18 | import org.springframework.util.ObjectUtils;
19 | import org.springframework.util.StringUtils;
20 |
21 | public enum StoredProcedureAttributeSource {
22 | INSTANCE;
23 |
24 | public StoredProcedureAttributes createFrom(Method method, JpaEntityMetadata> entityMetadata) {
25 |
26 | Assert.notNull(method, "Method must not be null");
27 | Assert.notNull(entityMetadata, "EntityMetadata must not be null");
28 |
29 | Procedure procedure = AnnotatedElementUtils.findMergedAnnotation(method, Procedure.class);
30 | Assert.notNull(procedure, "Method must have an @Procedure annotation");
31 |
32 | NamedStoredProcedureQuery namedStoredProc =
33 | tryFindAnnotatedNamedStoredProcedureQuery(method, entityMetadata, procedure);
34 |
35 | if (namedStoredProc != null) {
36 | return newProcedureAttributesFrom(method, namedStoredProc, procedure);
37 | }
38 |
39 | String procedureName = deriveProcedureNameFrom(method, procedure);
40 | if (ObjectUtils.isEmpty(procedureName)) {
41 | throw new IllegalArgumentException(
42 | "Could not determine name of procedure for @Procedure annotated method: " + method);
43 | }
44 |
45 | return new StoredProcedureAttributes(
46 | procedureName, createOutputProcedureParameterFrom(method, procedure));
47 | }
48 |
49 | private String deriveProcedureNameFrom(Method method, Procedure procedure) {
50 |
51 | if (StringUtils.hasText(procedure.value())) {
52 | return procedure.value();
53 | }
54 |
55 | String procedureName = procedure.procedureName();
56 | return StringUtils.hasText(procedureName) ? procedureName : method.getName();
57 | }
58 |
59 | private StoredProcedureAttributes newProcedureAttributesFrom(
60 | Method method, NamedStoredProcedureQuery namedStoredProc, Procedure procedure) {
61 |
62 | List outputParameters;
63 |
64 | if (!procedure.outputParameterName().isEmpty()) {
65 |
66 | // we give the output parameter definition from the @Procedure annotation precedence
67 | outputParameters =
68 | Collections.singletonList(createOutputProcedureParameterFrom(method, procedure));
69 | } else {
70 |
71 | // try to discover the output parameter
72 | outputParameters =
73 | extractOutputParametersFrom(namedStoredProc).stream() //
74 | .map(
75 | namedParameter ->
76 | new ProcedureParameter(
77 | namedParameter.name(), namedParameter.mode(), namedParameter.type())) //
78 | .collect(Collectors.toList());
79 | }
80 |
81 | return new StoredProcedureAttributes(namedStoredProc.name(), outputParameters, true);
82 | }
83 |
84 | private ProcedureParameter createOutputProcedureParameterFrom(
85 | Method method, Procedure procedure) {
86 |
87 | return new ProcedureParameter(
88 | procedure.outputParameterName(),
89 | procedure.refCursor() ? ParameterMode.REF_CURSOR : ParameterMode.OUT,
90 | method.getReturnType());
91 | }
92 |
93 | private List extractOutputParametersFrom(
94 | NamedStoredProcedureQuery namedStoredProc) {
95 |
96 | List outputParameters = new ArrayList<>();
97 |
98 | for (StoredProcedureParameter param : namedStoredProc.parameters()) {
99 |
100 | switch (param.mode()) {
101 | case OUT:
102 | case INOUT:
103 | case REF_CURSOR:
104 | outputParameters.add(param);
105 | break;
106 | case IN:
107 | default:
108 | continue;
109 | }
110 | }
111 |
112 | return outputParameters;
113 | }
114 |
115 | @Nullable
116 | private NamedStoredProcedureQuery tryFindAnnotatedNamedStoredProcedureQuery(
117 | Method method, JpaEntityMetadata> entityMetadata, Procedure procedure) {
118 |
119 | Assert.notNull(method, "Method must not be null");
120 | Assert.notNull(entityMetadata, "EntityMetadata must not be null");
121 | Assert.notNull(procedure, "Procedure must not be null");
122 |
123 | Class> entityType = entityMetadata.getJavaType();
124 |
125 | List queries = collectNamedStoredProcedureQueriesFrom(entityType);
126 |
127 | if (queries.isEmpty()) {
128 | return null;
129 | }
130 |
131 | String namedProcedureName = derivedNamedProcedureNameFrom(method, entityMetadata, procedure);
132 |
133 | for (NamedStoredProcedureQuery query : queries) {
134 |
135 | if (query.name().equals(namedProcedureName)) {
136 | return query;
137 | }
138 | }
139 |
140 | return null;
141 | }
142 |
143 | private String derivedNamedProcedureNameFrom(
144 | Method method, JpaEntityMetadata> entityMetadata, Procedure procedure) {
145 |
146 | return StringUtils.hasText(procedure.name()) //
147 | ? procedure.name() //
148 | : entityMetadata.getEntityName() + "." + method.getName();
149 | }
150 |
151 | private List collectNamedStoredProcedureQueriesFrom(
152 | Class> entityType) {
153 |
154 | List queries = new ArrayList<>();
155 |
156 | NamedStoredProcedureQueries namedQueriesAnnotation =
157 | AnnotatedElementUtils.findMergedAnnotation(entityType, NamedStoredProcedureQueries.class);
158 | if (namedQueriesAnnotation != null) {
159 | queries.addAll(Arrays.asList(namedQueriesAnnotation.value()));
160 | }
161 |
162 | NamedStoredProcedureQuery namedQueryAnnotation =
163 | AnnotatedElementUtils.findMergedAnnotation(entityType, NamedStoredProcedureQuery.class);
164 | if (namedQueryAnnotation != null) {
165 | queries.add(namedQueryAnnotation);
166 | }
167 |
168 | return queries;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/query/StoredProcedureAttributes.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.query;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 | import java.util.stream.Collectors;
6 | import java.util.stream.IntStream;
7 | import org.springframework.util.Assert;
8 | import org.springframework.util.StringUtils;
9 |
10 | public class StoredProcedureAttributes {
11 |
12 | static final String SYNTHETIC_OUTPUT_PARAMETER_NAME = "out";
13 |
14 | private final boolean namedStoredProcedure;
15 | private final String procedureName;
16 | private final List outputProcedureParameters;
17 |
18 | StoredProcedureAttributes(String procedureName, ProcedureParameter parameter) {
19 | this(procedureName, Collections.singletonList(parameter), false);
20 | }
21 |
22 | StoredProcedureAttributes(
23 | String procedureName,
24 | List outputProcedureParameters,
25 | boolean namedStoredProcedure) {
26 |
27 | Assert.notNull(procedureName, "ProcedureName must not be null");
28 | Assert.notNull(outputProcedureParameters, "OutputProcedureParameters must not be null");
29 | Assert.isTrue(
30 | outputProcedureParameters.size() != 1 || outputProcedureParameters.get(0) != null,
31 | "ProcedureParameters must not have size 1 with a null value");
32 |
33 | this.procedureName = procedureName;
34 | this.namedStoredProcedure = namedStoredProcedure;
35 |
36 | if (namedStoredProcedure) {
37 | this.outputProcedureParameters = outputProcedureParameters;
38 | } else {
39 | this.outputProcedureParameters = getParametersWithCompletedNames(outputProcedureParameters);
40 | }
41 | }
42 |
43 | private List getParametersWithCompletedNames(
44 | List procedureParameters) {
45 |
46 | return IntStream.range(0, procedureParameters.size()) //
47 | .mapToObj(i -> getParameterWithCompletedName(procedureParameters.get(i), i)) //
48 | .collect(Collectors.toList());
49 | }
50 |
51 | private ProcedureParameter getParameterWithCompletedName(ProcedureParameter parameter, int i) {
52 |
53 | return new ProcedureParameter(
54 | completeOutputParameterName(i, parameter.getName()),
55 | parameter.getMode(),
56 | parameter.getType());
57 | }
58 |
59 | private String completeOutputParameterName(int i, String paramName) {
60 |
61 | return StringUtils.hasText(paramName) //
62 | ? paramName //
63 | : createSyntheticParameterName(i);
64 | }
65 |
66 | private String createSyntheticParameterName(int i) {
67 | return SYNTHETIC_OUTPUT_PARAMETER_NAME + (i == 0 ? "" : i);
68 | }
69 |
70 | public String getProcedureName() {
71 | return procedureName;
72 | }
73 |
74 | public boolean isNamedStoredProcedure() {
75 | return namedStoredProcedure;
76 | }
77 |
78 | public List getOutputProcedureParameters() {
79 | return outputProcedureParameters;
80 | }
81 |
82 | public boolean hasReturnValue() {
83 |
84 | if (getOutputProcedureParameters().isEmpty()) return false;
85 |
86 | Class> outputType = getOutputProcedureParameters().get(0).getType();
87 | return !(void.class.equals(outputType) || Void.class.equals(outputType));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/CrudMethodMetadataContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import org.springframework.data.jpa.repository.support.CrudMethodMetadata;
4 | import reactor.core.publisher.Mono;
5 | import reactor.util.context.Context;
6 |
7 | /**
8 | * @author Bao.Ngo
9 | */
10 | public class CrudMethodMetadataContextHolder {
11 |
12 | private static final Object KEY = CrudMethodMetadataContextHolder.class;
13 |
14 | private CrudMethodMetadataContextHolder() {}
15 |
16 | public static Mono currentCrudMethodMetadata() {
17 | return Mono.deferContextual(
18 | c -> {
19 | if (c.hasKey(KEY)) {
20 | return c.get(KEY);
21 | }
22 | return Mono.error(
23 | () -> new IllegalStateException("CrudMethodMetadata may not be correctly setup"));
24 | });
25 | }
26 |
27 | public static Context set(Mono metadata) {
28 | return Context.of(KEY, metadata);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/CrudMethodMetadataPostProcessor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import jakarta.persistence.LockModeType;
4 | import jakarta.persistence.QueryHint;
5 | import java.lang.reflect.Method;
6 | import java.util.HashSet;
7 | import java.util.Optional;
8 | import java.util.Set;
9 | import java.util.concurrent.ConcurrentHashMap;
10 | import java.util.concurrent.ConcurrentMap;
11 | import java.util.function.Predicate;
12 | import java.util.function.Supplier;
13 | import org.aopalliance.intercept.MethodInterceptor;
14 | import org.aopalliance.intercept.MethodInvocation;
15 | import org.springframework.aop.framework.ProxyFactory;
16 | import org.springframework.core.annotation.AnnotatedElementUtils;
17 | import org.springframework.core.annotation.AnnotationUtils;
18 | import org.springframework.data.jpa.repository.EntityGraph;
19 | import org.springframework.data.jpa.repository.Lock;
20 | import org.springframework.data.jpa.repository.Meta;
21 | import org.springframework.data.jpa.repository.QueryHints;
22 | import org.springframework.data.jpa.repository.support.CrudMethodMetadata;
23 | import org.springframework.data.jpa.repository.support.MutableQueryHints;
24 | import org.springframework.data.repository.core.RepositoryInformation;
25 | import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
26 | import org.springframework.lang.Nullable;
27 | import org.springframework.util.Assert;
28 | import org.springframework.util.ReflectionUtils;
29 | import reactor.core.publisher.Flux;
30 | import reactor.core.publisher.Mono;
31 |
32 | /**
33 | * @author Bao.Ngo
34 | */
35 | public class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor {
36 |
37 | @Override
38 | public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
39 | factory.addAdvice(
40 | new CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor(
41 | repositoryInformation));
42 | }
43 |
44 | static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor {
45 |
46 | private static final ConcurrentMap> METADATA_CACHE =
47 | new ConcurrentHashMap<>();
48 |
49 | private final Set implementations = new HashSet<>();
50 |
51 | CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) {
52 | ReflectionUtils.doWithMethods(
53 | repositoryInformation.getRepositoryInterface(),
54 | implementations::add,
55 | method -> !repositoryInformation.isQueryMethod(method));
56 | }
57 |
58 | @Override
59 | public Object invoke(MethodInvocation invocation) throws Throwable {
60 | Method method = invocation.getMethod();
61 | Object proceeded = invocation.proceed();
62 |
63 | if (!implementations.contains(method)) {
64 | return proceeded;
65 | }
66 |
67 | Mono methodMetadata = METADATA_CACHE.get(method);
68 |
69 | if (methodMetadata == null) {
70 | Supplier supplier = () -> new DefaultCrudMethodMetadata(method);
71 | methodMetadata = Mono.fromSupplier(supplier).cache();
72 | METADATA_CACHE.putIfAbsent(method, methodMetadata);
73 | }
74 |
75 | if (proceeded instanceof Mono> mono) {
76 | return mono.contextWrite(CrudMethodMetadataContextHolder.set(methodMetadata));
77 | }
78 |
79 | if (proceeded instanceof Flux> flux) {
80 | return flux.contextWrite(CrudMethodMetadataContextHolder.set(methodMetadata));
81 | }
82 |
83 | return proceeded;
84 | }
85 | }
86 |
87 | private static class DefaultCrudMethodMetadata implements CrudMethodMetadata {
88 |
89 | private final @Nullable LockModeType lockModeType;
90 | private final org.springframework.data.jpa.repository.support.QueryHints queryHints;
91 | private final org.springframework.data.jpa.repository.support.QueryHints queryHintsForCount;
92 | private final @Nullable String comment;
93 | private final Optional entityGraph;
94 | private final Method method;
95 |
96 | DefaultCrudMethodMetadata(Method method) {
97 | Assert.notNull(method, "Method must not be null");
98 |
99 | this.lockModeType = findLockModeType(method);
100 | this.queryHints = findQueryHints(method, it -> true);
101 | this.queryHintsForCount = findQueryHints(method, QueryHints::forCounting);
102 | this.comment = findComment(method);
103 | this.entityGraph = findEntityGraph(method);
104 | this.method = method;
105 | }
106 |
107 | private static Optional findEntityGraph(Method method) {
108 | return Optional.ofNullable(
109 | AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class));
110 | }
111 |
112 | @Nullable
113 | private static LockModeType findLockModeType(Method method) {
114 | Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class);
115 | return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation);
116 | }
117 |
118 | private static org.springframework.data.jpa.repository.support.QueryHints findQueryHints(
119 | Method method, Predicate annotationFilter) {
120 | MutableQueryHints queryHints = new MutableQueryHints();
121 | QueryHints queryHintsAnnotation =
122 | AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class);
123 | if (queryHintsAnnotation != null && annotationFilter.test(queryHintsAnnotation)) {
124 | for (QueryHint hint : queryHintsAnnotation.value()) {
125 | queryHints.add(hint.name(), hint.value());
126 | }
127 | }
128 |
129 | QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class);
130 | if (queryHintAnnotation != null) {
131 | queryHints.add(queryHintAnnotation.name(), queryHintAnnotation.value());
132 | }
133 |
134 | return queryHints;
135 | }
136 |
137 | @Nullable
138 | private static String findComment(Method method) {
139 | Meta annotation = AnnotatedElementUtils.findMergedAnnotation(method, Meta.class);
140 | return annotation == null ? null : (String) AnnotationUtils.getValue(annotation, "comment");
141 | }
142 |
143 | @Nullable
144 | @Override
145 | public LockModeType getLockModeType() {
146 | return lockModeType;
147 | }
148 |
149 | @Override
150 | public org.springframework.data.jpa.repository.support.QueryHints getQueryHints() {
151 | return queryHints;
152 | }
153 |
154 | @Override
155 | public org.springframework.data.jpa.repository.support.QueryHints getQueryHintsForCount() {
156 | return queryHintsForCount;
157 | }
158 |
159 | @Override
160 | public String getComment() {
161 | return comment;
162 | }
163 |
164 | @Override
165 | public Optional getEntityGraph() {
166 | return entityGraph;
167 | }
168 |
169 | @Override
170 | public Method getMethod() {
171 | return method;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/PersistenceExceptionHandlerPostProcessor.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import jakarta.persistence.NoResultException;
4 | import org.aopalliance.intercept.MethodInterceptor;
5 | import org.aopalliance.intercept.MethodInvocation;
6 | import org.hibernate.reactive.stage.Stage;
7 | import org.springframework.aop.framework.ProxyFactory;
8 | import org.springframework.data.repository.core.RepositoryInformation;
9 | import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
10 | import reactor.core.publisher.Flux;
11 | import reactor.core.publisher.Mono;
12 |
13 | /**
14 | * @author Bao.Ngo
15 | */
16 | public class PersistenceExceptionHandlerPostProcessor implements RepositoryProxyPostProcessor {
17 |
18 | private final Stage.SessionFactory sessionFactory;
19 |
20 | public PersistenceExceptionHandlerPostProcessor(Stage.SessionFactory sessionFactory) {
21 | this.sessionFactory = sessionFactory;
22 | }
23 |
24 | @Override
25 | public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
26 | factory.addAdvice(new PersistenceExceptionHandlerInterceptor(sessionFactory));
27 | }
28 |
29 | static class PersistenceExceptionHandlerInterceptor implements MethodInterceptor {
30 |
31 | private final Stage.SessionFactory sessionFactory;
32 |
33 | public PersistenceExceptionHandlerInterceptor(Stage.SessionFactory sessionFactory) {
34 | this.sessionFactory = sessionFactory;
35 | }
36 |
37 | @Override
38 | public Object invoke(MethodInvocation invocation) throws Throwable {
39 | Object proceed = invocation.proceed();
40 | if (proceed instanceof Mono> mono) {
41 | return mono.onErrorResume(NoResultException.class, e -> Mono.empty());
42 | } else if (proceed instanceof Flux> flux) {
43 | return flux.onErrorResume(NoResultException.class, e -> Mono.empty());
44 | }
45 |
46 | return proceed;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/ReactiveJpaRepositoryFactory.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import com.htech.data.jpa.reactive.core.StageReactiveJpaEntityOperations;
4 | import com.htech.data.jpa.reactive.repository.query.ReactiveJpaQueryLookupStrategy;
5 | import com.htech.data.jpa.reactive.repository.query.ReactiveJpaQueryMethodFactory;
6 | import com.htech.data.jpa.reactive.repository.query.ReactiveQueryRewriterProvider;
7 | import jakarta.persistence.EntityManagerFactory;
8 | import jakarta.persistence.metamodel.Metamodel;
9 | import java.util.Optional;
10 | import org.hibernate.reactive.stage.Stage;
11 | import org.springframework.beans.factory.BeanClassLoaderAware;
12 | import org.springframework.data.domain.Persistable;
13 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
14 | import org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation;
15 | import org.springframework.data.jpa.repository.support.JpaPersistableEntityInformation;
16 | import org.springframework.data.repository.core.EntityInformation;
17 | import org.springframework.data.repository.core.RepositoryInformation;
18 | import org.springframework.data.repository.core.RepositoryMetadata;
19 | import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
20 | import org.springframework.data.repository.query.QueryLookupStrategy;
21 | import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
22 | import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
23 |
24 | /**
25 | * @author Bao.Ngo
26 | */
27 | public class ReactiveJpaRepositoryFactory extends ReactiveRepositoryFactorySupport
28 | implements BeanClassLoaderAware {
29 |
30 | protected final StageReactiveJpaEntityOperations entityOperations;
31 | protected final Stage.SessionFactory sessionFactory;
32 | protected final EntityManagerFactory entityManagerFactory;
33 | protected ClassLoader classLoader;
34 | protected EscapeCharacter escapeCharacter;
35 |
36 | protected ReactiveJpaQueryMethodFactory queryMethodFactory;
37 | protected ReactiveQueryRewriterProvider queryRewriterProvider;
38 |
39 | public ReactiveJpaRepositoryFactory(
40 | StageReactiveJpaEntityOperations entityOperations,
41 | Stage.SessionFactory sessionFactory,
42 | EntityManagerFactory entityManagerFactory) {
43 | this.entityOperations = entityOperations;
44 | this.sessionFactory = sessionFactory;
45 | this.entityManagerFactory = entityManagerFactory;
46 | }
47 |
48 | @Override
49 | @SuppressWarnings({"rawtypes"})
50 | public EntityInformation getEntityInformation(Class domainClass) {
51 | Metamodel metamodel = sessionFactory.getMetamodel();
52 | if (Persistable.class.isAssignableFrom(domainClass)) {
53 | return new JpaPersistableEntityInformation(
54 | domainClass, metamodel, entityManagerFactory.getPersistenceUnitUtil());
55 | } else {
56 | return new JpaMetamodelEntityInformation(
57 | domainClass, metamodel, entityManagerFactory.getPersistenceUnitUtil());
58 | }
59 | }
60 |
61 | @Override
62 | protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
63 | EntityInformation, Object> entityInformation =
64 | getEntityInformation(repositoryInformation.getDomainType());
65 | ReactiveJpaRepositoryImplementation, ?> repository =
66 | getTargetRepositoryViaReflection(
67 | repositoryInformation, entityInformation, sessionFactory, entityOperations);
68 | //
69 | // repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
70 | repository.setEscapeCharacter(escapeCharacter);
71 |
72 | return repository;
73 | }
74 |
75 | // protected ReactiveJpaRepositoryImplementation, ?> getTargetRepositoryViaReflection1(
76 | // RepositoryInformation repositoryInformation, EntityInformation, Object>
77 | // entityInformation) {
78 | // Class> repositoryBaseClass = repositoryInformation.getRepositoryBaseClass();
79 | //
80 | // return Optional.ofNullable(ReflectionUtils.findMethod(
81 | // repositoryBaseClass,
82 | // "createInstance",
83 | // JpaEntityInformation.class,
84 | // MutinyReactiveJpaEntityOperations.class,
85 | // Stage.SessionFactory.class,
86 | // ClassLoader.class))
87 | // .map(m -> {
88 | // ReflectionUtils.makeAccessible(m);
89 | // try {
90 | // return (ReactiveJpaRepositoryImplementation, ?>)
91 | // m.invoke(null, entityInformation, entityOperations, sessionFactory,
92 | // classLoader);
93 | // } catch (IllegalAccessException | InvocationTargetException e) {
94 | // throw new RuntimeException(e.getMessage(), e);
95 | // }
96 | // }).orElseThrow(() -> new RuntimeException("Method createInstance is not found"));
97 | //
98 | //// return (ReactiveJpaRepositoryImplementation, ?>)
99 | //// method.invoke(null, entityInformation, sessionFactory, classLoader);
100 | // }
101 |
102 | @Override
103 | protected Class> getRepositoryBaseClass(RepositoryMetadata metadata) {
104 | // return SimpleReactiveJpaRepository.class;
105 | return SimpleReactiveJpaRepository.class;
106 | }
107 |
108 | public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
109 | this.escapeCharacter = escapeCharacter;
110 | }
111 |
112 | @Override
113 | protected Optional getQueryLookupStrategy(
114 | QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) {
115 | return Optional.of(
116 | ReactiveJpaQueryLookupStrategy.create(
117 | entityManagerFactory,
118 | sessionFactory,
119 | queryMethodFactory,
120 | key,
121 | (ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider,
122 | queryRewriterProvider,
123 | escapeCharacter));
124 | }
125 |
126 | @Override
127 | public void setBeanClassLoader(ClassLoader classLoader) {
128 | super.setBeanClassLoader(classLoader);
129 | this.classLoader = classLoader;
130 | }
131 |
132 | @Override
133 | public RepositoryMetadata getRepositoryMetadata(Class> repositoryInterface) {
134 | return super.getRepositoryMetadata(repositoryInterface);
135 | }
136 |
137 | public ReactiveJpaQueryMethodFactory getQueryMethodFactory() {
138 | return queryMethodFactory;
139 | }
140 |
141 | public void setQueryMethodFactory(ReactiveJpaQueryMethodFactory queryMethodFactory) {
142 | this.queryMethodFactory = queryMethodFactory;
143 | }
144 |
145 | public ReactiveQueryRewriterProvider getQueryRewriterProvider() {
146 | return queryRewriterProvider;
147 | }
148 |
149 | public void setQueryRewriterProvider(ReactiveQueryRewriterProvider queryRewriterProvider) {
150 | this.queryRewriterProvider = queryRewriterProvider;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/ReactiveJpaRepositoryFactoryBean.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import com.htech.data.jpa.reactive.core.StageReactiveJpaEntityOperations;
4 | import com.htech.data.jpa.reactive.repository.query.DefaultReactiveJpaQueryExtractor;
5 | import com.htech.data.jpa.reactive.repository.query.ReactiveJpaQueryMethodFactory;
6 | import com.htech.data.jpa.reactive.repository.query.ReactiveQueryRewriterProvider;
7 | import jakarta.persistence.EntityManagerFactory;
8 | import java.io.Serializable;
9 | import java.util.*;
10 | import org.springframework.beans.BeansException;
11 | import org.springframework.beans.factory.BeanClassLoaderAware;
12 | import org.springframework.beans.factory.ListableBeanFactory;
13 | import org.springframework.beans.factory.ObjectProvider;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.context.ApplicationContext;
16 | import org.springframework.context.ApplicationContextAware;
17 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
18 | import org.springframework.data.querydsl.EntityPathResolver;
19 | import org.springframework.data.querydsl.SimpleEntityPathResolver;
20 | import org.springframework.data.repository.Repository;
21 | import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
22 | import org.springframework.data.repository.core.support.RepositoryFactorySupport;
23 | import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
24 | import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
25 | import org.springframework.lang.Nullable;
26 |
27 | /**
28 | * @author Bao.Ngo
29 | */
30 | public class ReactiveJpaRepositoryFactoryBean<
31 | T extends Repository, S, ID extends Serializable>
32 | extends RepositoryFactoryBeanSupport
33 | implements ApplicationContextAware, BeanClassLoaderAware {
34 |
35 | private @Nullable ApplicationContext applicationContext;
36 | private StageReactiveJpaEntityOperations entityOperations;
37 |
38 | private EntityPathResolver entityPathResolver;
39 |
40 | private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
41 |
42 | protected ReactiveJpaRepositoryFactoryBean(Class extends T> repositoryInterface) {
43 | super(repositoryInterface);
44 | }
45 |
46 | @Override
47 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
48 | this.applicationContext = applicationContext;
49 | }
50 |
51 | @Override
52 | protected RepositoryFactorySupport createRepositoryFactory() {
53 | ReactiveJpaRepositoryFactory factory =
54 | new ReactiveJpaRepositoryFactory(
55 | entityOperations,
56 | entityOperations.sessionFactory(),
57 | applicationContext.getBean("entityManagerFactory", EntityManagerFactory.class));
58 | factory.setEscapeCharacter(escapeCharacter);
59 | // TODO
60 | factory.setQueryMethodFactory(
61 | new ReactiveJpaQueryMethodFactory(new DefaultReactiveJpaQueryExtractor()));
62 | factory.setQueryRewriterProvider(ReactiveQueryRewriterProvider.simple());
63 |
64 | // RepositoryMetadata repositoryMetadata = factory.getRepositoryMetadata(getObjectType());
65 | // factory.addRepositoryProxyPostProcessor(new ValueAdapterInterceptorProxyPostProcessor());
66 | // factory.addRepositoryProxyPostProcessor(new SessionAwareProxyPostProcessor());
67 | factory.addRepositoryProxyPostProcessor(new CrudMethodMetadataPostProcessor());
68 | factory.addRepositoryProxyPostProcessor(
69 | new SessionAwarePostProcessor(entityOperations.sessionFactory()));
70 | factory.addRepositoryProxyPostProcessor(
71 | new PersistenceExceptionHandlerPostProcessor(entityOperations.sessionFactory()));
72 |
73 | return factory;
74 | }
75 |
76 | @Override
77 | protected Optional
78 | createDefaultQueryMethodEvaluationContextProvider(ListableBeanFactory beanFactory) {
79 | return Optional.of(new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(beanFactory));
80 | }
81 |
82 | // @Autowired
83 | public void setEntityOperations(@Nullable StageReactiveJpaEntityOperations entityOperations) {
84 | this.entityOperations = entityOperations;
85 | }
86 |
87 | @Autowired
88 | public void setEntityPathResolver(ObjectProvider resolver) {
89 | this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE);
90 | }
91 |
92 | public void setEscapeCharacter(char escapeCharacter) {
93 | this.escapeCharacter = EscapeCharacter.of(escapeCharacter);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/com/htech/data/jpa/reactive/repository/support/ReactiveJpaRepositoryImplementation.java:
--------------------------------------------------------------------------------
1 | package com.htech.data.jpa.reactive.repository.support;
2 |
3 | import com.htech.data.jpa.reactive.repository.ReactiveJpaRepository;
4 | import com.htech.data.jpa.reactive.repository.ReactiveJpaSpecificationExecutor;
5 | import org.springframework.data.jpa.repository.query.EscapeCharacter;
6 | import org.springframework.data.repository.NoRepositoryBean;
7 |
8 | /**
9 | * @author Bao.Ngo
10 | */
11 | @NoRepositoryBean
12 | public interface ReactiveJpaRepositoryImplementation
13 | extends ReactiveJpaRepository, ReactiveJpaSpecificationExecutor