hibernatequarkusquarkus-panachemutinyquarkus-reactive

Creating a related entity after persisting an entity using Reactive/Mutiny


I am relatively new to Quarkus, and much so with Reactive Java/Mutiny, but I am trying to create a related database entity after creating another entity. Example: Saving a User object, then creating a related UserProfile object that has a one-to-one relationship with User.

In other frameworks I would do the following (within a REST API function call):

  1. Use API payload to create a User instance, user1.
  2. With user instance saved, create a UserProfile instance with profile.user_id = user1.id
  3. Wrap all the above in a transaction

I've created a UserResource below. This seems to work fine -- both HTTP response and database contain the expected data, but is there a better way? It seems like this could be done with just one transaction instead?


@ApplicationScoped
@Path("/api/user")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {

  @Inject
  UserRepository userRepository;

  @Inject
  UserProfileRepository userProfileRepository;

  @GET
  public Uni<List<UserEntity>> list() {
    return userRepository.listAll();
  }

  @GET
  @Path("/{id}")
  public Uni<UserEntity> get(Long id) {
    return userRepository.findById(id);
  }

  @POST
  @Path("/")
  public Uni<Response> create(UserEntity user) {
    return Panache
        .withTransaction(() -> userRepository.persist(user))
        .onItem()
        .transformToUni(item -> {
          System.out.println(item);
          var userProfile = new UserProfileEntity();
          userProfile.setUser(item);
          userProfile.setDisplayName("My Name");
          return Panache.withTransaction(() -> userProfileRepository.persist(userProfile));
        })
        .invoke(item -> System.out.println(item))
        .replaceWith(Response.ok(user).status(Status.CREATED)::build);
  }
}

Solution

  • Yes, you shouldn't have to use two transactions.

    The solution may change depending on the mapping you are using.

    Repository pattern without cascade option:

      @POST
      @Path("/")
      public Uni<Response> create(UserEntity user) {            
       System.out.println(user);
       var userProfile = new UserProfileEntity();
       userProfile.setUser(user);
       // If bidirectional association: user.setUserProfile(userProfile)
       userProfile.setDisplayName("My Name");
    
        return Panache
            .withTransaction(() -> userRepository
                .persist(user)
                .call( () -> userProfileRepository.persist(userProfile)) 
            )
            .invoke(item -> System.out.println(item))
            .replaceWith(Response.ok(user).status(Status.CREATED)::build);
      }
    

    Repository pattern with cascade option:

      @POST
      @Path("/")
      public Uni<Response> create(UserEntity user) {            
       System.out.println(user);
       var userProfile = new UserProfileEntity();
       userProfile.setUser(user);
       // If bidirectional association: user.setUserProfile(userProfile)
       userProfile.setDisplayName("My Name");
    
        return Panache
            .withTransaction(() -> userRepository.persist(user))
            .invoke(item -> System.out.println(item))
            .replaceWith(Response.ok(user).status(Status.CREATED)::build);
      }
    

    Panache using cascading, without repository:

      @POST
      @Path("/")
      public Uni<Response> create(UserEntity user) {            
       System.out.println(user);
       var userProfile = new UserProfileEntity();
       userProfile.setUser(user);
       // If bidirectional association: user.setUserProfile(userProfile)
       userProfile.setDisplayName("My Name");
    
        return Panache.withTransaction(user::persist)
            .invoke(item -> System.out.println(item))
            .replaceWith(Response.ok(user).status(Status.CREATED)::build);
      }
    

    Here's the link to the documentation of cascade type PERSIST in Hibernate ORM documentation