Reputation: 141
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
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
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
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