quarkusquarkus-panachemutinyquarkus-reactive

How can I turn this combine().all () into a synchronous chain due a Hibernate Reactive limitation?


I'm using Quarkus version 3.13.0 and also using Hibernate Reactive.

I have created the code below using Mutiny's Uni.combine() to query two entities and then persist a third entity (Join Table) using both results.

I was bitten by this Hibernate limitation that prevents us to use the same session in parallel executions.

I'm new to Mutiny, so I would like a help to turn this code into a synchronous chain that would fix the error I'm getting, because I couldn't find any other method other than combine() that provides a BiFunction.

@POST
@Path("{userId}/teams/{businessUnitId}")
public Uni<RestResponse<UserDto>> assignToTeam(Long userId, Long businessUnitId, @RestQuery String pRole) {
    if (businessUnitId == null || userId == null) {
        throw new WebApplicationException(MSG_INVALID_PAYLOAD, 422);
    }

    return Uni.combine()
            .all()
            .unis(Panache.withTransaction(() -> userRepo.findById(userId)
                    .onItem()
                    .ifNull()
                    .failWith(new WebApplicationException(MSG_RESOURCE_NOT_FOUND, NOT_FOUND))),
                    Panache.withTransaction(() -> businessUnitRepo.findById(businessUnitId)
                            .onItem()
                            .ifNull()
                            .failWith(new WebApplicationException(MSG_RESOURCE_NOT_FOUND, NOT_FOUND))))
            .with((user, bunit) -> {
                BusinessUnitsUsers buu = new BusinessUnitsUsersBuilder().parentEntity(bunit)
                        .childEntity(user)
                        .role(pRole)
                        .build();
                return user.assingToTeam(buu);
            })
            .chain(r -> Panache.withTransaction(() -> userRepo.persist(r))
                    .map(user -> RestResponse.ok(userMapper.toDto(user, usersBusinessUnitContext))));
}

This is the error I get:

2024-08-06 13:20:30,632 ERROR [http-problem] (vert.x-eventloop-thread-1) status=500, title="Internal Server Error": io.smallrye.mutiny.CompositeException: Multiple exceptions caught:
        [Exception 0] java.lang.IllegalStateException: Session/EntityManager is closed
        [Exception 1] io.vertx.core.impl.NoStackTraceThrowable: Transaction already complete
        at io.smallrye.mutiny.groups.UniOnFailure.lambda$call$3(UniOnFailure.java:108)

Solution

  • This should work:

    @POST
    @Path("{userId}/teams/{businessUnitId}")
    public Uni<RestResponse<UserDto>> assignToTeam(Long userId, Long businessUnitId, @RestQuery String pRole) {
        if (businessUnitId == null || userId == null) {
            throw new WebApplicationException(MSG_INVALID_PAYLOAD, 422);
        }
    
        return Panache
            .withTransaction(() -> userRepo
                        .findById(userId)
                        .onItem().ifNull().failWith(new WebApplicationException(MSG_RESOURCE_NOT_FOUND, NOT_FOUND))
            )
            .chain( user -> Panache
                .withTransaction(() -> businessUnitRepo
                    .findById(businessUnitId)
                    .onItem().ifNull().failWith(new WebApplicationException(MSG_RESOURCE_NOT_FOUND, NOT_FOUND))
                )
                .chain( bunit -> {
                    BusinessUnitsUsers buu = new BusinessUnitsUsersBuilder()
                            .parentEntity(bunit)
                            .childEntity(user)
                            .role(pRole)
                            .build();
                    return user.assingToTeam(buu);
                } )
                .chain(r -> Panache
                    .withTransaction(() -> userRepo.persist(r) )
                    .map(user -> RestResponse.ok(userMapper.toDto(user, usersBusinessUnitContext)))
                );
            )
    }