Skip to content

Commit 26e8896

Browse files
committed
Panache Support multiple persistence unit in Hibernate Reactive
* Integration test for multiple reactive persistence units and Panache Backported from ORM the handling of different persistence units in entity in Panache * `@WithSessionOnDemand` works only with the default persistence unit * withSession overload to take the PU name * Execute update without entity runs on default session * Repository flush() flushes all PUs Refactor * Use ComputingCache instead of a Map<String, LazyValue> * Keep track of onDemand created sessions to close them accordingly * create map of entity => PU in kotlin as well (identical to what happens in Hibernate ORM) * Aligned reactive blocking to orm repositories by using @GenerateBridge to call getSession * getSession shouldn't be static to be substituted by @GenerateBridge * Added Panache.withTransaction overload to select persistence unit * Updated Panache documentation * Support @WithSession("pu-name") and @WithTransaction("pu-name") * Added test to verify the same session is used * Removed dependency from Kotlin panache module to Java, created two new Panache classes inside the Kotlin module and changed the tests accordingly.
1 parent 49cdd0f commit 26e8896

File tree

42 files changed

+1352
-173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1352
-173
lines changed

docs/src/main/asciidoc/hibernate-reactive-panache.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,10 @@ If no annotations are found.
772772

773773
== Multiple Persistence Units
774774

775-
Hibernate Reactive in Quarkus currently does not support multiple persistence units.
775+
Hibernate Reactive with Panache supports multiple persistence units.
776+
When using multiple persistence units, Panache automatically uses the correct persistence unit based on the entity's configuration.
777+
778+
For configuration details on setting up multiple persistence units, refer to the xref:hibernate-orm.adoc#multiple-persistence-units[Multiple Persistence Units] section in the Hibernate ORM guide.
776779

777780
[[transactions]]
778781
== Sessions and Transactions
@@ -783,13 +786,15 @@ For example, if a Panache entity method is invoked in a Jakarta REST resource me
783786
For other cases, there are both a declarative and a programmatic way to ensure the session is opened.
784787
You can annotate a CDI business method that returns `Uni` with the `@WithSession` annotation.
785788
The method will be intercepted and the returned `Uni` will be triggered within a scope of a reactive session.
789+
If you have multiple persistence units, you can specify which one to use by providing the persistence unit name as the annotation value: `@WithSession("my-persistence-unit")`. If not specified, the default persistence unit is used.
786790
Alternatively, you can use the `Panache.withSession()` method to achieve the same effect.
787791

788792
NOTE: Note that a Panache entity may not be used from a blocking thread. See also xref:getting-started-reactive.adoc[Getting Started With Reactive] guide that explains the basics of reactive principles in Quarkus.
789793

790794
Also make sure to wrap methods that modify the database or involve multiple queries (e.g. `entity.persist()`) within a transaction.
791795
You can annotate a CDI business method that returns `Uni` with the `@WithTransaction` annotation.
792796
The method will be intercepted and the returned `Uni` is triggered within a transaction boundary.
797+
If you have multiple persistence units, you can specify which one to use by providing the persistence unit name as the annotation value: `@WithTransaction("my-persistence-unit")`. If not specified, the default persistence unit is used.
793798
Alternatively, you can use the `Panache.withTransaction()` method for the same effect.
794799

795800
IMPORTANT: You cannot use the `@Transactional` annotation with Hibernate Reactive for your transactions: you must use `@WithTransaction`, and your annotated method must return a `Uni` to be non-blocking.

extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/WithSession.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.quarkus.hibernate.reactive.panache.common;
22

3+
import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME;
4+
35
import java.lang.annotation.ElementType;
46
import java.lang.annotation.Inherited;
57
import java.lang.annotation.Retention;
68
import java.lang.annotation.RetentionPolicy;
79
import java.lang.annotation.Target;
810

11+
import jakarta.enterprise.util.Nonbinding;
912
import jakarta.interceptor.InterceptorBinding;
1013

1114
/**
@@ -25,4 +28,12 @@
2528
@Retention(value = RetentionPolicy.RUNTIME)
2629
public @interface WithSession {
2730

31+
/**
32+
* The name of the persistence unit. If not specified, the default persistence unit is used.
33+
*
34+
* @return the persistence unit name
35+
*/
36+
@Nonbinding
37+
String value() default DEFAULT_PERSISTENCE_UNIT_NAME;
38+
2839
}

extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/WithTransaction.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.quarkus.hibernate.reactive.panache.common;
22

3+
import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME;
4+
35
import java.lang.annotation.ElementType;
46
import java.lang.annotation.Inherited;
57
import java.lang.annotation.Retention;
68
import java.lang.annotation.RetentionPolicy;
79
import java.lang.annotation.Target;
810

11+
import jakarta.enterprise.util.Nonbinding;
912
import jakarta.interceptor.InterceptorBinding;
1013

1114
/**
@@ -27,4 +30,12 @@
2730
@Retention(value = RetentionPolicy.RUNTIME)
2831
public @interface WithTransaction {
2932

33+
/**
34+
* The name of the persistence unit. If not specified, the default persistence unit is used.
35+
*
36+
* @return the persistence unit name
37+
*/
38+
@Nonbinding
39+
String value() default DEFAULT_PERSISTENCE_UNIT_NAME;
40+
3041
}

extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package io.quarkus.hibernate.reactive.panache.common.runtime;
22

3+
import static io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME;
4+
35
import java.util.ArrayList;
6+
import java.util.Arrays;
47
import java.util.Collections;
58
import java.util.List;
69
import java.util.Map;
710
import java.util.Map.Entry;
11+
import java.util.stream.Collectors;
812
import java.util.stream.Stream;
913

1014
import jakarta.persistence.LockModeType;
@@ -19,6 +23,11 @@
1923
import io.smallrye.mutiny.Uni;
2024

2125
public abstract class AbstractJpaOperations<PanacheQueryType> {
26+
private static volatile Map<String, String> entityToPersistenceUnit = Collections.emptyMap();
27+
28+
public static void setEntityToPersistenceUnit(Map<String, String> map) {
29+
entityToPersistenceUnit = Collections.unmodifiableMap(map);
30+
}
2231

2332
// FIXME: make it configurable?
2433
static final long TIMEOUT_MS = 5000;
@@ -34,7 +43,7 @@ protected abstract PanacheQueryType createPanacheQuery(Uni<Mutiny.Session> sessi
3443
// Instance methods
3544

3645
public Uni<Void> persist(Object entity) {
37-
return persist(getSession(), entity);
46+
return persist(getSession(entity.getClass()), entity);
3847
}
3948

4049
public Uni<Void> persist(Uni<Mutiny.Session> sessionUni, Object entity) {
@@ -67,20 +76,38 @@ public Uni<Void> persist(Stream<?> entities) {
6776
}
6877

6978
public Uni<Void> persist(Object... entities) {
70-
return getSession().chain(session -> session.persistAll(entities));
79+
Map<String, List<Object>> sessions = Arrays.stream(entities)
80+
.collect(Collectors.groupingBy(e -> entityToPersistenceUnit.get(e.getClass().getName())));
81+
82+
List<Uni<Void>> results = new ArrayList<>();
83+
for (Entry<String, List<Object>> entry : sessions.entrySet()) {
84+
results.add(getSession(entry.getKey()).chain(session -> session.persistAll(entry.getValue().toArray())));
85+
}
86+
87+
return Uni.combine().all().unis(results).discardItems();
7188
}
7289

7390
public Uni<Void> delete(Object entity) {
74-
return getSession().chain(session -> session.remove(entity));
91+
return getSession(entity.getClass()).chain(session -> session.remove(entity));
7592
}
7693

7794
public boolean isPersistent(Object entity) {
78-
Mutiny.Session current = SessionOperations.getCurrentSession();
79-
return current != null ? current.contains(entity) : false;
95+
Session currentSession = getCurrentSession(entity.getClass());
96+
if (currentSession == null) {
97+
// No active session so object is surely non-persistent
98+
return false;
99+
}
100+
101+
return currentSession.contains(entity);
102+
}
103+
104+
public Session getCurrentSession(Class<?> entityClass) {
105+
String persistenceUnitName = entityToPersistenceUnit.get(entityClass.getName());
106+
return SessionOperations.getCurrentSession(persistenceUnitName);
80107
}
81108

82-
public Uni<Void> flush() {
83-
return getSession().chain(Session::flush);
109+
public Uni<Void> flush(Object entity) {
110+
return getSession(entity.getClass()).chain(Session::flush);
84111
}
85112

86113
public int paramCount(Object[] params) {
@@ -95,11 +122,11 @@ public int paramCount(Map<String, Object> params) {
95122
// Queries
96123

97124
public Uni<?> findById(Class<?> entityClass, Object id) {
98-
return getSession().chain(session -> session.find(entityClass, id));
125+
return getSession(entityClass).chain(session -> session.find(entityClass, id));
99126
}
100127

101128
public Uni<?> findById(Class<?> entityClass, Object id, LockModeType lockModeType) {
102-
return getSession()
129+
return getSession(entityClass)
103130
.chain(session -> session.find(entityClass, id, LockModeConverter.convertToLockMode(lockModeType)));
104131
}
105132

@@ -108,7 +135,7 @@ public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Object..
108135
}
109136

110137
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Sort sort, Object... params) {
111-
Uni<Mutiny.Session> session = getSession();
138+
Uni<Mutiny.Session> session = getSession(entityClass);
112139
if (PanacheJpaUtil.isNamedQuery(panacheQuery)) {
113140
String namedQuery = panacheQuery.substring(1);
114141
if (sort != null) {
@@ -128,7 +155,7 @@ public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Map<Stri
128155
}
129156

130157
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Sort sort, Map<String, Object> params) {
131-
Uni<Mutiny.Session> session = getSession();
158+
Uni<Mutiny.Session> session = getSession(entityClass);
132159
if (PanacheJpaUtil.isNamedQuery(panacheQuery)) {
133160
String namedQuery = panacheQuery.substring(1);
134161
if (sort != null) {
@@ -177,13 +204,13 @@ public Uni<List<?>> list(Class<?> entityClass, String query, Sort sort, Paramete
177204

178205
public PanacheQueryType findAll(Class<?> entityClass) {
179206
String query = "FROM " + PanacheJpaUtil.getEntityName(entityClass);
180-
Uni<Mutiny.Session> session = getSession();
207+
Uni<Mutiny.Session> session = getSession(entityClass);
181208
return createPanacheQuery(session, query, null, null, null);
182209
}
183210

184211
public PanacheQueryType findAll(Class<?> entityClass, Sort sort) {
185212
String query = "FROM " + PanacheJpaUtil.getEntityName(entityClass);
186-
Uni<Mutiny.Session> session = getSession();
213+
Uni<Mutiny.Session> session = getSession(entityClass);
187214
return createPanacheQuery(session, query, null, PanacheJpaUtil.toOrderBy(sort), null);
188215
}
189216

@@ -196,7 +223,7 @@ public Uni<List<?>> listAll(Class<?> entityClass, Sort sort) {
196223
}
197224

198225
public Uni<Long> count(Class<?> entityClass) {
199-
return getSession()
226+
return getSession(entityClass)
200227
.chain(session -> session
201228
.createSelectionQuery("FROM " + PanacheJpaUtil.getEntityName(entityClass), entityClass)
202229
.getResultCount());
@@ -206,13 +233,13 @@ public Uni<Long> count(Class<?> entityClass) {
206233
public Uni<Long> count(Class<?> entityClass, String panacheQuery, Object... params) {
207234

208235
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
209-
return (Uni) getSession().chain(session -> {
236+
return (Uni) getSession(entityClass).chain(session -> {
210237
String namedQueryName = panacheQuery.substring(1);
211238
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
212239
return bindParameters(session.createNamedQuery(namedQueryName, Long.class), params).getSingleResult();
213240
});
214241

215-
return getSession().chain(session -> bindParameters(
242+
return getSession(entityClass).chain(session -> bindParameters(
216243
session.createSelectionQuery(PanacheJpaUtil.createQueryForCount(entityClass, panacheQuery, paramCount(params)),
217244
Object.class),
218245
params).getResultCount())
@@ -223,13 +250,13 @@ public Uni<Long> count(Class<?> entityClass, String panacheQuery, Object... para
223250
public Uni<Long> count(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {
224251

225252
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
226-
return getSession().chain(session -> {
253+
return getSession(entityClass).chain(session -> {
227254
String namedQueryName = panacheQuery.substring(1);
228255
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
229256
return bindParameters(session.createNamedQuery(namedQueryName, Long.class), params).getSingleResult();
230257
});
231258

232-
return getSession().chain(session -> bindParameters(
259+
return getSession(entityClass).chain(session -> bindParameters(
233260
session.createSelectionQuery(PanacheJpaUtil.createQueryForCount(entityClass, panacheQuery, paramCount(params)),
234261
Object.class),
235262
params).getResultCount())
@@ -258,7 +285,7 @@ public Uni<Boolean> exists(Class<?> entityClass, String query, Parameters params
258285
}
259286

260287
public Uni<Long> deleteAll(Class<?> entityClass) {
261-
return getSession().chain(
288+
return getSession(entityClass).chain(
262289
session -> session.createMutationQuery("DELETE FROM " + PanacheJpaUtil.getEntityName(entityClass))
263290
.executeUpdate()
264291
.map(Integer::longValue));
@@ -272,20 +299,20 @@ public Uni<Boolean> deleteById(Class<?> entityClass, Object id) {
272299
if (entity == null) {
273300
return Uni.createFrom().item(false);
274301
}
275-
return getSession().chain(session -> session.remove(entity).map(v -> true));
302+
return getSession(entityClass).chain(session -> session.remove(entity).map(v -> true));
276303
});
277304
}
278305

279306
public Uni<Long> delete(Class<?> entityClass, String panacheQuery, Object... params) {
280307

281308
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
282-
return getSession().chain(session -> {
309+
return getSession(entityClass).chain(session -> {
283310
String namedQueryName = panacheQuery.substring(1);
284311
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
285312
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate().map(Integer::longValue);
286313
});
287314

288-
return getSession().chain(session -> bindParameters(
315+
return getSession(entityClass).chain(session -> bindParameters(
289316
session.createMutationQuery(PanacheJpaUtil.createDeleteQuery(entityClass, panacheQuery, paramCount(params))),
290317
params)
291318
.executeUpdate().map(Integer::longValue))
@@ -296,13 +323,13 @@ public Uni<Long> delete(Class<?> entityClass, String panacheQuery, Object... par
296323
public Uni<Long> delete(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {
297324

298325
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
299-
return getSession().chain(session -> {
326+
return getSession(entityClass).chain(session -> {
300327
String namedQueryName = panacheQuery.substring(1);
301328
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
302329
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate().map(Integer::longValue);
303330
});
304331

305-
return getSession().chain(session -> bindParameters(
332+
return getSession(entityClass).chain(session -> bindParameters(
306333
session.createMutationQuery(PanacheJpaUtil.createDeleteQuery(entityClass, panacheQuery, paramCount(params))),
307334
params)
308335
.executeUpdate().map(Integer::longValue))
@@ -314,15 +341,15 @@ public Uni<Long> delete(Class<?> entityClass, String query, Parameters params) {
314341
return delete(entityClass, query, params.map());
315342
}
316343

317-
public IllegalStateException implementationInjectionMissing() {
344+
public static IllegalStateException implementationInjectionMissing() {
318345
return new IllegalStateException(
319346
"This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?");
320347
}
321348

322349
public Uni<Integer> executeUpdate(Class<?> entityClass, String panacheQuery, Object... params) {
323350

324351
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
325-
return (Uni) getSession().chain(session -> {
352+
return (Uni) getSession(entityClass).chain(session -> {
326353
String namedQueryName = panacheQuery.substring(1);
327354
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
328355
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate();
@@ -337,7 +364,7 @@ public Uni<Integer> executeUpdate(Class<?> entityClass, String panacheQuery, Obj
337364
public Uni<Integer> executeUpdate(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {
338365

339366
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
340-
return (Uni) getSession().chain(session -> {
367+
return (Uni) getSession(entityClass).chain(session -> {
341368
String namedQueryName = panacheQuery.substring(1);
342369
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
343370
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate();
@@ -364,8 +391,18 @@ public Uni<Integer> update(Class<?> entityClass, String query, Object... params)
364391
//
365392
// Static helpers
366393

367-
public static Uni<Mutiny.Session> getSession() {
368-
return SessionOperations.getSession();
394+
public Uni<Mutiny.Session> getSession() {
395+
return getSession(DEFAULT_PERSISTENCE_UNIT_NAME);
396+
}
397+
398+
public Uni<Mutiny.Session> getSession(Class<?> clazz) {
399+
String className = clazz.getName();
400+
String persistenceUnitName = entityToPersistenceUnit.get(className);
401+
return getSession(persistenceUnitName);
402+
}
403+
404+
public Uni<Mutiny.Session> getSession(String persistenceUnitName) {
405+
return SessionOperations.getSession(persistenceUnitName);
369406
}
370407

371408
public static Mutiny.Query<?> bindParameters(Mutiny.Query<?> query, Object[] params) {
@@ -395,13 +432,21 @@ public static <T extends Mutiny.AbstractQuery> T bindParameters(T query, Map<Str
395432
return query;
396433
}
397434

398-
public static Uni<Integer> executeUpdate(String query, Object... params) {
399-
return getSession().chain(session -> bindParameters(session.createMutationQuery(query), params)
400-
.executeUpdate());
435+
/**
436+
* Execute update on default persistence unit
437+
*/
438+
public Uni<Integer> executeUpdate(String query, Object... params) {
439+
return getSession(DEFAULT_PERSISTENCE_UNIT_NAME)
440+
.chain(session -> bindParameters(session.createMutationQuery(query), params)
441+
.executeUpdate());
401442
}
402443

403-
public static Uni<Integer> executeUpdate(String query, Map<String, Object> params) {
404-
return getSession().chain(session -> bindParameters(session.createMutationQuery(query), params)
405-
.executeUpdate());
444+
/**
445+
* Execute update on default persistence unit
446+
*/
447+
public Uni<Integer> executeUpdate(String query, Map<String, Object> params) {
448+
return getSession(DEFAULT_PERSISTENCE_UNIT_NAME)
449+
.chain(session -> bindParameters(session.createMutationQuery(query), params)
450+
.executeUpdate());
406451
}
407452
}

0 commit comments

Comments
 (0)