Quarkus Topic
Part 1: Configuring Datasources
Quarkus uses a unified configuration approach for datasources. You configure everything in application.properties.
Basic PostgreSQL Configuration
properties
# Datasource configuration quarkus.datasource.db-kind=postgresql quarkus.datasource.username=quarkus_user quarkus.datasource.password=quarkus_pass quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_db # Hibernate configuration quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.log.sql=true
MySQL Configuration
properties
quarkus.datasource.db-kind=mysql quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/quarkus_db quarkus.datasource.username=root quarkus.datasource.password=secret
Multiple Datasources
properties
# Default datasource quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/main_db # Secondary datasource quarkus.datasource.users.db-kind=mysql quarkus.datasource.users.jdbc.url=jdbc:mysql://localhost:3306/users_db # Hibernate for secondary datasource quarkus.hibernate-orm.users.database.generation=update
Part 2: Hibernate ORM with Panache
Panache comes in two flavors: Repository Pattern and Active Record Pattern. Both significantly reduce boilerplate.
Active Record Pattern
The entity extends PanacheEntity and contains its own persistence logic.
java
@Entity
@Table(name = "person")
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public String status;
// Custom business methods
public static Person findByName(String name) {
return find("name", name).firstResult();
}
public static List<Person> findAlive() {
return list("status", "alive");
}
public static long deleteSteves() {
return delete("name", "Steve");
}
}
Repository Pattern
More familiar if you come from Spring Data. Separates persistence logic from the entity.
java
// Entity (pure data model)
@Entity
@Table(name = "person")
public class Person {
@Id
@GeneratedValue
private Long id;
public String name;
public LocalDate birth;
public String status;
// Getters and setters...
}
// Repository
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
public Person findByName(String name) {
return find("name", name).firstResult();
}
public List<Person> findAlive() {
return list("status", "alive");
}
public List<Person> findBornAfter(LocalDate date) {
return find("birth > ?1", date).list();
}
}
Part 3: Entity Relationships, Queries, and Repositories
Entity Relationships
java
@Entity
public class Person extends PanacheEntity {
public String name;
// One-to-One
@OneToOne(cascade = CascadeType.ALL)
public Address address;
// One-to-Many
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
public List<Phone> phones = new ArrayList<>();
// Many-to-Many
@ManyToMany
@JoinTable(name = "person_team")
public List<Team> teams = new ArrayList<>();
}
@Entity
public class Phone extends PanacheEntity {
public String number;
@ManyToOne
public Person person;
}
Query Methods
Panache provides several ways to query:
1. Simple Queries
java
// Basic operations
Person person = Person.findById(1L);
List<Person> people = Person.listAll();
// Simple where clauses
Person steve = Person.find("name", "Steve").firstResult();
List<Person> alivePeople = Person.list("status", "alive");
// Complex queries
List<Person> adults = Person.find("birth < ?1",
LocalDate.now().minusYears(18)).list();
// Named queries
@NamedQuery(name = "Person.getAlive",
query = "FROM Person WHERE status = 'alive'")
public class Person extends PanacheEntity { ... }
List<Person> alive = Person.find("#Person.getAlive").list();
2. Advanced Queries with PanacheQuery
java
PanacheQuery<Person> query = Person.find("status", "alive");
// Pagination
List<Person> firstPage = query.page(0, 20).list();
List<Person> secondPage = query.nextPage().list();
// Streaming (for large datasets)
query.stream().forEach(person -> {
processPerson(person);
});
// Single results
Person person = query.firstResult();
3. Repository Pattern Usage
java
@Inject
PersonRepository personRepository;
public void businessMethod() {
Person person = personRepository.findById(1L);
List<Person> people = personRepository.listAll();
List<Person> alive = personRepository.findAlive();
// Count
long total = personRepository.count();
long aliveCount = personRepository.count("status", "alive");
// Update
personRepository.update("status = 'inactive' where birth < ?1",
LocalDate.now().minusYears(65));
}
Part 4: Reactive SQL Clients
For truly reactive applications, Quarkus offers reactive SQL clients that work without Hibernate.
Reactive PostgreSQL Client
Dependencies
xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
Configuration
properties
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_db quarkus.datasource.username=quarkus_user quarkus.datasource.password=quarkus_pass
Usage Example
java
@Path("/reactive-users")
@ApplicationScoped
public class ReactiveUserResource {
@Inject
io.vertx.mutiny.pgclient.PgPool client;
// Create table (in production, use Flyway)
@PostConstruct
void setup() {
client.query("DROP TABLE IF EXISTS reactive_users").execute()
.flatMap(r -> client.query("""
CREATE TABLE reactive_users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
)
""").execute())
.subscribe().with(
success -> System.out.println("Table created"),
failure -> System.out.println("Error: " + failure.getMessage())
);
}
// Create user
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Uni<Long> createUser(User user) {
return client.preparedQuery("""
INSERT INTO reactive_users (name, email)
VALUES ($1, $2) RETURNING id
""")
.execute(Tuple.of(user.name, user.email))
.map(pgRowSet -> {
// Extract the generated ID
return pgRowSet.iterator().next().getLong("id");
});
}
// Get all users
@GET
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<User>> getAllUsers() {
return client.query("SELECT id, name, email FROM reactive_users")
.execute()
.map(pgRowSet -> {
List<User> list = new ArrayList<>();
for (Row row : pgRowSet) {
list.add(new User(
row.getLong("id"),
row.getString("name"),
row.getString("email")
));
}
return list;
});
}
// Get user by ID
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<User> getUserById(@PathParam("id") Long id) {
return client.preparedQuery("SELECT id, name, email FROM reactive_users WHERE id = $1")
.execute(Tuple.of(id))
.map(pgRowSet -> {
if (pgRowSet.size() == 0) {
return null;
}
Row row = pgRowSet.iterator().next();
return new User(
row.getLong("id"),
row.getString("name"),
row.getString("email")
);
});
}
}
// Simple User DTO
class User {
public Long id;
public String name;
public String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
Transaction Management
Imperative Transactions
java
@ApplicationScoped
public class PersonService {
@Inject
EntityManager em;
@Transactional
public void createPerson(Person person) {
// This runs in a transaction
person.persist();
}
@Transactional
public void transferMoney(Long from, Long to, BigDecimal amount) {
Person fromPerson = Person.findById(from);
Person toPerson = Person.findById(to);
fromPerson.balance = fromPerson.balance.subtract(amount);
toPerson.balance = toPerson.balance.add(amount);
// If any exception occurs, transaction rolls back automatically
}
}
Reactive Transactions
java
@Inject
Mutiny.Session session;
public Uni<Void> createUserReactive(User user) {
return session.withTransaction(tx -> {
return session.persist(user);
});
}
Database Migration with Flyway
properties
# Flyway configuration quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:db/migration
sql
-- src/main/resources/db/migration/V1.0.0__Create_person_table.sql
CREATE TABLE person (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
birth DATE,
status VARCHAR(50)
);
Key Takeaways
-
Panache Choice: Use Active Record for simple CRUD, Repository for complex business logic
-
Query Simplicity: Panache eliminates 90% of boilerplate repository code
-
Reactive When Needed: Use reactive clients for high-concurrency, non-blocking scenarios
-
Build-Time Optimization: Quarkus optimizes Hibernate at build time for faster startup
-
Unified Configuration: All datasource configuration in one place
The combination of Quarkus + Panache makes database interactions in Java feel modern and productive, while still giving you access to full Hibernate power when you need it.
Comments (0)
No comments yet. Be the first to comment!