extensions/hibernate-reactive/deployment/src/main/resources/META-INF/quarkus-skill.md
Add quarkus-hibernate-reactive + a reactive SQL client (reactive-pg-client, reactive-mysql-client, etc.). Dev Services auto-starts a database container and manages the schema — no manual config needed in dev/test.
Standard JPA annotations — same as Hibernate ORM:
@Entity
public class Task {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String title;
public boolean completed;
@CreationTimestamp
public LocalDateTime createdAt;
}
The simplest approach — inject Mutiny.Session directly and use @Transactional. Methods must return Uni:
@Inject Mutiny.Session session;
@Transactional
public Uni<Task> create(Task task) {
return session.persist(task).replaceWith(task);
}
@Transactional
public Uni<Task> findById(Long id) {
return session.find(Task.class, id);
}
@Transactional
public Uni<List<Task>> listAll() {
return session.createQuery("FROM Task", Task.class).getResultList();
}
@Transactional
public Uni<Task> update(Long id, Task updated) {
return session.find(Task.class, id)
.onItem().ifNotNull().invoke(existing -> {
existing.title = updated.title;
existing.completed = updated.completed;
});
}
@Transactional
public Uni<Boolean> delete(Long id) {
return session.find(Task.class, id)
.onItem().ifNotNull().transformToUni(task ->
session.remove(task).replaceWith(true))
.onItem().ifNull().continueWith(false);
}
Use Mutiny.SessionFactory when you need explicit transaction control:
@Inject Mutiny.SessionFactory sessionFactory;
public Uni<Task> create(Task task) {
return sessionFactory.withTransaction(session ->
session.persist(task).replaceWith(task)
);
}
public Uni<Task> update(Long id, Task updated) {
return sessionFactory.withTransaction(session ->
session.find(Task.class, id)
.onItem().ifNotNull().invoke(existing -> {
existing.title = updated.title;
existing.completed = updated.completed;
})
);
}
@Inject Mutiny.Session + @Transactional (preferably) or @Inject Mutiny.SessionFactory + sessionFactory.withTransaction(session -> ...) (legacy code or advanced usage) — automatically commits on success, rolls back on exception. Changes to managed entities are flushed automatically on commit.session.persist(entity) returns Uni<Void> — chain .replaceWith(entity) to get the entity back.session.find(Type.class, id) returns Uni<Type> — may resolve to null if not found.Lazy initialization is not supported in Hibernate Reactive. Fetch lazy associations explicitly using Mutiny.fetch():
// Returns Uni<Post> with comments initialized
return session.find(Post.class, id)
.call(post -> Mutiny.fetch(post.getComments()));
// Returns Uni<List<Comment>> directly
return session.find(Post.class, id)
.chain(post -> Mutiny.fetch(post.getComments()));
.call() — fetches the association, returns the parent entity (Post) with the collection initialized..chain() — fetches the association, returns the fetched collection (List<Comment>) directly.Accessing an unfetched lazy collection throws LazyInitializationException.
@Entity
@NamedQuery(name = "Task.findCompleted", query = "FROM Task WHERE completed = true")
public class Task { ... }
// Usage
session.createNamedQuery("Task.findCompleted", Task.class).getResultList()
@QuarkusTest normally with REST Assured.session.persist() returns Uni<Void>, not the entity — use .replaceWith(entity).session.find() returns null (not an exception) for missing entities — handle with .onItem().ifNull().LazyInitializationException if not fetched — always use Mutiny.fetch() before accessing them, or use DTOs to control which data is serialized.@ReactiveTransactional — it is deprecated. Use @Transactional instead.@Transactional with Panache's @WithTransaction/@WithSession in the same reactive pipeline.Thread.sleep(), no blocking I/O.