0

I'm trying to develop a multitenancy app with quarkus, hibernate-reactive and postgres.

Hibernate-reactive supports multitenancy by letting implement ReactiveConnectionPool :

http://hibernate.org/reactive/documentation/1.0/reference/html_single/#_custom_connection_management_and_multitenancy

Defining hibernate.vertx.pool.class in application.properties seems to be ignored.

Is this feature of hibernate-reactive integrated in quarkus ?

Has anyone used this feature before?

msg
  • 3
  • 2

2 Answers2

0

Multitenancy for Hibernate Reactive has not been integrated in Quarkus yet.

I've just created an issue for it: https://github.com/quarkusio/quarkus/issues/15959

Davide D'Alto
  • 7,421
  • 2
  • 16
  • 30
0

It's not supported on a pretty deep level, so no way (or at least I couldn't find any) of somehow adding it by putting up a custom extension.

What I did is a small dirty trick - my uses of @ReactiveTransactional with a custom @MyAppTransactional (call it however you want) interceptor.

This is solution is very specific for Postgres, and uses schema multi-tenancy method.

@Interceptor
@MyAppTransactional
// right after @ReactiveTransactional
@Priority(Interceptor.Priority.PLATFORM_BEFORE + 200 + 1)
@RequiredArgsConstructor
class MyAppTransactionalInterceptor {

    // this is a request-scoped bean that holds my tenant id
    @Inject
    TenantId tenantId;

    @Inject
    Mutiny.Session session;

    @Inject
    Validator validator;

    @Inject
    MultiTenancyConfig multiTenancyConfig;

    @AroundInvoke
    Object intercept(InvocationContext ic) throws Exception {
        if (!multiTenancyConfig.enabled()) {
            return ic.proceed();
        }

        Class<?> returnType = ic.getMethod().getReturnType();

        if (returnType != Uni.class) {
            throw new RuntimeException("only Uni return types are supported with transactional methods");
        }

        if (!session.isOpen()) {
            // just a sanity check - inheritance from @ReactiveTransactional makes sure that transaction is active by now
            throw new IllegalStateException(
                "unexpected state: Hibernate session is not active in tenant transaction interceptor");
        }

        String sessionTenantId = tenantId.get();
        if (!validator.validate(sessionTenantId).isEmpty()) {
            // double check just in case to avoid potential SQL injection
            // (hint appreciated: how to properly escape postgres string literal here instead?)
            throw new IllegalStateException(
                "unexpected state: Hibernate session is not active in tenant transaction interceptor");
        }

        return session.createNativeQuery("SET LOCAL SCHEMA '" + tenantId.get() + "'")
            .executeUpdate()
            .flatMap((ignore) -> {
                try {
                    return (Uni<?>) ic.proceed();
                } catch (Exception e) {
                    return Uni.createFrom().failure(e);
                }
            });
    }
}

Annotation itself:

@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
@ReactiveTransactional
public @interface MyAppTransactional {

}

Note for myself: need to improve this to avoid unneeded calls to "SET SCHEMA" with nested transactional method invocations.