smillis12
smillis12

Reputation: 141

Cannot persist data with quarkus + hibernate reactive panache entity

I'm trying to create a Hello World application with quarkus. I have many troubles making it work.

Here are my classes :

import java.time.Duration;

import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.PanacheEntityBase;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.hibernate.reactive.panache.common.WithSessionOnDemand;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Cacheable
@Entity
@Table(name = "category", schema = "administration")
@WithSessionOnDemand
public class Category extends PanacheEntityBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    public String name;

    public static Uni<Category > add(Category cat) {
        return Panache
                .withTransaction(cat::persist)
                .replaceWith(cat)
                .ifNoItem()
                .after(Duration.ofMillis(10000))
                .fail()
                .onFailure()
                .transform(t -> new IllegalStateException(t));
    }

}


   @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<List<Category>> list2() {


        // Create and persist a new category
        Category category = new Category();
        category.name = "test quarkus 3";
        Category.add(category)
                .onItem().transform(id -> id.id)
                .subscribe().with(e -> System.out.println(e));


        // Return the list of categories
        return Category.listAll();
    }

When I hit the GET endpoint, the category is created. But If I hit it 3 times in a row, I get the following error : ERROR [io.qua.mut.run.MutinyInfrastructure] (vert.x-eventloop-thread-1) Mutiny had to drop the following exception: java.lang.IllegalStateException: java.lang.IllegalStateException: No current Mutiny.Session found - no reactive session was found in the context and the context was not marked to open a new session lazily - you might need to annotate the business method with @WithSession at org.test.category.Category.lambda$1(Category.java:39)

So I tried to annotate my Category class with @WithSession and then with @WithSessionOnDemand but nothing worked. What am I doing wrong ?

Upvotes: 3

Views: 3887

Answers (3)

EliuX
EliuX

Reputation: 12685

You need to run your query, no matter if it is for reading or writting inside a transaction: Panache.withTransaction(...). If you read the documentation of this function you will see

Performs the given work within the scope of a database transaction, automatically flushing the session. The transaction will be rolled back if the work completes with an uncaught exception, or if Mutiny.Transaction#markForRollback() is called.

Upvotes: 0

Johnny X
Johnny X

Reputation: 11

I ran into a similar problem, for a hello world app you might not need @WithSession.

You can check how the Quarkus team uses it on their demo app quarkus-super-heroes

You can also check how the problem you experience has been reported on that same app gh issue here, and the team's comments.


Trying to keep your hello app as close as your code as possible I may suggest the following changes to your controller while keeping the Entity as is.

Category.java (just like you have it, I don't use an administration schema, that's all)

@Cacheable
@Entity
@Table(name = "category")
public class Category extends PanacheEntityBase {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  public static Uni<Category> add(Category cat) {
    return Panache
        .withTransaction(cat::persist)
        .replaceWith(cat)
        .ifNoItem()
        .after(Duration.ofMillis(10000))
        .fail()
        .onFailure()
        .transform(IllegalStateException::new);

/*get/set omitted for brevity*/
  }

CategoryResource.java (your controller)

@Path("/category")
@Produces(MediaType.APPLICATION_JSON)
public class CategoryResource {
  @GET
  public Uni<List<Category>> listAllCategories() {
    Category category = new Category();
    category.setName("name");

    return Category.add(category)
        .replaceWith(Category.findAll().list());
  }

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  public Uni<List<Category>> createCategory(final Category category) {
    return Category.add(category)
        .replaceWith(Category.findAll().list());
  }
}

I took the liberty of adding a POST method.

Here you have a github repo with a quick working example -> SO-cannot-persist-data-with-quarkus-hibernate-reactive-panache-entity


For a codebase larger than a learning example, you may need a @WithSession and its family.


screenshot of code and REST call

Upvotes: 0

Lebecca
Lebecca

Reputation: 2878

you need to chain your create and query operations in a stream style thus they can access the session from thin air.

Entity class:

import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;

@Entity
@Cacheable
public class Fruit extends PanacheEntity {

  @Column(length = 40, unique = false)
  public String name;

}

Service Class:

import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.panache.common.Sort;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import java.time.Duration;
import java.util.List;

// first method
@Path("/fruits")
@ApplicationScoped
public class FruitResource {

  @GET
  public Uni<List<Fruit>> get() {
    return Fruit.listAll(Sort.by("name"));
  }

  @POST
  public Uni<List<Fruit>> create(Fruit fruit) {

    Uni<Long> createStage = Panache
        .withTransaction(fruit::persist)
        .replaceWith(fruit)
        .ifNoItem()
        .after(Duration.ofMillis(10000))
        .fail()
        .onFailure()
        .transform(t -> {
          System.out.println("exception happen lalal");
          t.printStackTrace();
          return new IllegalStateException(t);
        })
        .onItem().transform(item -> {
          System.out.println("fruit is created: " + item.id);
          return item.id;
        });
    Uni<List<Fruit>> queryAllFruitsStage = Fruit.listAll();

    return createStage.chain(() -> queryAllFruitsStage);

  }
}

// second method
@Path("/fruits")
@ApplicationScoped
public class FruitResource {

  @GET
  public Uni<List<Fruit>> get() {
    return Fruit.listAll(Sort.by("name"));
  }

  @POST
  public Uni<List<Fruit>> create(Fruit fruit) {

    return Panache
        .withTransaction(fruit::persist)
        .replaceWith(fruit)
        .ifNoItem()
        .after(Duration.ofMillis(10000))
        .fail()
        .onFailure()
        .transform(t -> {
          System.out.println("exception happen lalal");
          t.printStackTrace();
          return new IllegalStateException(t);
        })
        .onItem()
        .transformToUni(item -> Fruit.listAll());
  }
}

The curl test:

curl --location 'localhost:8080/fruits' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
    "name": "fruit1"
}'

you may have a look at the Mutiny Patterns part at the docs Quarkus - Mutiny Primer and have a better understanding of reactive programming.

Upvotes: 1

Related Questions