Skip to content

Commit c3f6a89

Browse files
committed
Fix using coroutine criteria executor with Hibernate (#2775)
(cherry picked from commit 2fab536)
1 parent f91bd3d commit c3f6a89

File tree

6 files changed

+109
-6
lines changed

6 files changed

+109
-6
lines changed

data-hibernate-jpa/src/main/java/io/micronaut/data/hibernate/operations/HibernateJpaOperations.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import io.micronaut.data.operations.reactive.ReactiveRepositoryOperations;
5050
import io.micronaut.data.runtime.convert.DataConversionService;
5151
import io.micronaut.data.runtime.operations.ExecutorAsyncOperations;
52+
import io.micronaut.data.runtime.operations.ExecutorAsyncOperationsSupportingCriteria;
5253
import io.micronaut.data.runtime.operations.ExecutorReactiveOperations;
5354
import io.micronaut.transaction.TransactionOperations;
5455
import jakarta.inject.Named;
@@ -568,7 +569,8 @@ public ExecutorAsyncOperations async() {
568569
synchronized (this) { // double check
569570
executorAsyncOperations = this.asyncOperations;
570571
if (executorAsyncOperations == null) {
571-
executorAsyncOperations = new ExecutorAsyncOperations(
572+
executorAsyncOperations = new ExecutorAsyncOperationsSupportingCriteria(
573+
this,
572574
this,
573575
executorService != null ? executorService : newLocalThreadPool()
574576
);

data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/async/AbstractAsyncSpecificationInterceptor.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,15 @@ public abstract class AbstractAsyncSpecificationInterceptor<T, R> extends Abstra
5656
*/
5757
protected AbstractAsyncSpecificationInterceptor(RepositoryOperations operations) {
5858
super(operations);
59-
if (operations instanceof AsyncCapableRepository) {
60-
this.asyncOperations = ((AsyncCapableRepository) operations).async();
59+
if (operations instanceof AsyncCapableRepository asyncCapableRepository) {
60+
this.asyncOperations = asyncCapableRepository.async();
6161
} else {
6262
throw new DataAccessException("Datastore of type [" + operations.getClass() + "] does not support asynchronous operations");
6363
}
6464
if (operations instanceof AsyncCriteriaRepositoryOperations asyncCriteriaRepositoryOperations) {
6565
asyncCriteriaOperations = asyncCriteriaRepositoryOperations;
66+
} else if (asyncOperations instanceof AsyncCriteriaRepositoryOperations asyncCriteriaRepositoryOperations) {
67+
asyncCriteriaOperations = asyncCriteriaRepositoryOperations;
6668
} else if (operations instanceof AsyncCriteriaCapableRepository repository) {
6769
asyncCriteriaOperations = repository.async();
6870
} else {

data-runtime/src/main/java/io/micronaut/data/runtime/operations/ExecutorAsyncOperations.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.micronaut.data.runtime.operations;
1717

18+
import io.micronaut.core.annotation.Internal;
1819
import io.micronaut.core.annotation.NonNull;
1920
import io.micronaut.core.propagation.PropagatedContext;
2021
import io.micronaut.core.util.ArgumentUtils;
@@ -62,11 +63,12 @@ public ExecutorAsyncOperations(@NonNull RepositoryOperations operations, @NonNul
6263
this.executor = executor;
6364
}
6465

65-
private <T> CompletableFuture<T> supplyAsync(Supplier<T> supplier) {
66+
@Internal
67+
<T> CompletableFuture<T> supplyAsync(Supplier<T> supplier) {
6668
CompletableFuture<T> cf = new CompletableFuture<>();
6769
PropagatedContext propagatedContext = PropagatedContext.getOrEmpty();
6870
CompletableFuture.supplyAsync(PropagatedContext.wrapCurrent(supplier), executor).whenComplete((value, throwable) -> {
69-
try (PropagatedContext.Scope scope = propagatedContext.propagate()) {
71+
try (PropagatedContext.Scope ignore = propagatedContext.propagate()) {
7072
if (throwable != null) {
7173
cf.completeExceptionally(throwable);
7274
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2017-2024 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.data.runtime.operations;
17+
18+
import io.micronaut.core.annotation.Internal;
19+
import io.micronaut.data.operations.CriteriaRepositoryOperations;
20+
import io.micronaut.data.operations.RepositoryOperations;
21+
import io.micronaut.data.operations.async.AsyncCriteriaRepositoryOperations;
22+
import jakarta.persistence.criteria.CriteriaBuilder;
23+
import jakarta.persistence.criteria.CriteriaDelete;
24+
import jakarta.persistence.criteria.CriteriaQuery;
25+
import jakarta.persistence.criteria.CriteriaUpdate;
26+
27+
import java.util.List;
28+
import java.util.concurrent.CompletionStage;
29+
import java.util.concurrent.Executor;
30+
31+
/**
32+
* A variation of {@link ExecutorAsyncOperations} that supports {@link AsyncCriteriaRepositoryOperations}.
33+
* @author Denis Stepanov
34+
*/
35+
@Internal
36+
public final class ExecutorAsyncOperationsSupportingCriteria extends ExecutorAsyncOperations implements AsyncCriteriaRepositoryOperations {
37+
38+
private final CriteriaRepositoryOperations criteriaRepositoryOperations;
39+
40+
/**
41+
* Default constructor.
42+
*
43+
* @param operations The target operations
44+
* @param criteriaRepositoryOperations The criteria operations
45+
* @param executor The executor to use.
46+
*/
47+
public ExecutorAsyncOperationsSupportingCriteria(RepositoryOperations operations,
48+
CriteriaRepositoryOperations criteriaRepositoryOperations,
49+
Executor executor) {
50+
super(operations, executor);
51+
this.criteriaRepositoryOperations = criteriaRepositoryOperations;
52+
}
53+
54+
@Override
55+
public CriteriaBuilder getCriteriaBuilder() {
56+
return criteriaRepositoryOperations.getCriteriaBuilder();
57+
}
58+
59+
@Override
60+
public <R> CompletionStage<R> findOne(CriteriaQuery<R> query) {
61+
return supplyAsync(() -> criteriaRepositoryOperations.findOne(query));
62+
}
63+
64+
@Override
65+
public <T> CompletionStage<List<T>> findAll(CriteriaQuery<T> query) {
66+
return supplyAsync(() -> criteriaRepositoryOperations.findAll(query));
67+
}
68+
69+
@Override
70+
public CompletionStage<Number> updateAll(CriteriaUpdate<Number> query) {
71+
return supplyAsync(() -> criteriaRepositoryOperations.updateAll(query).orElse(null));
72+
}
73+
74+
@Override
75+
public CompletionStage<Number> deleteAll(CriteriaDelete<Number> query) {
76+
return supplyAsync(() -> criteriaRepositoryOperations.deleteAll(query).orElse(null));
77+
}
78+
}

doc-examples/hibernate-example-kotlin/src/main/kotlin/example/ParentSuspendRepository.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import io.micronaut.core.annotation.NonNull
44
import io.micronaut.data.annotation.Join
55
import io.micronaut.data.annotation.Repository
66
import io.micronaut.data.repository.GenericRepository
7+
import io.micronaut.data.repository.jpa.kotlin.CoroutineJpaSpecificationExecutor
78
import java.util.*
89
import jakarta.transaction.Transactional
910

1011
@Repository
11-
interface ParentSuspendRepository : GenericRepository<Parent, Int> {
12+
interface ParentSuspendRepository : GenericRepository<Parent, Int>, CoroutineJpaSpecificationExecutor<Parent> {
1213

1314
@Join(value = "children", type = Join.Type.FETCH)
1415
suspend fun findById(id: Int): Optional<Parent>

doc-examples/hibernate-example-kotlin/src/test/kotlin/example/HibernateTxTest.kt

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package example
22

3+
import io.micronaut.data.repository.jpa.criteria.PredicateSpecification
4+
import io.micronaut.data.repository.jpa.criteria.QuerySpecification
5+
import io.micronaut.data.runtime.criteria.get
36
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
47
import org.junit.jupiter.api.Assertions.assertTrue
58
import jakarta.inject.Inject
@@ -154,4 +157,19 @@ class HibernateTxTest {
154157
Assertions.assertEquals(0, service.countForCustomDb())
155158
}
156159

160+
@Test
161+
@Order(12)
162+
fun coroutineCriteria() {
163+
runBlocking {
164+
val parent = Parent("abc", Collections.emptyList())
165+
val saved = repositorySuspended.save(parent)
166+
167+
val found1 = repositorySuspended.findOne(QuerySpecification { root, query, criteriaBuilder -> criteriaBuilder.equal(root.get<String>("name"), "abc")})
168+
Assertions.assertEquals(found1!!.id, saved.id)
169+
170+
val found2 = repositorySuspended.findOne(PredicateSpecification { root, criteriaBuilder -> criteriaBuilder.equal(root.get<String>("name"), "abc")})
171+
Assertions.assertEquals(found2!!.id, saved.id)
172+
}
173+
}
174+
157175
}

0 commit comments

Comments
 (0)