Quarkus is a supersonic Java framework built with a cloud-native first mentality. It’s blazing fast in startup and has a much smaller memory footprint compared to Spring Boot. Quarkus is well integrated with Hibernate and works flawlessly. To simplify the Hibernate complexity, one can use Panache to achieve spring-data-repository-like results. In this article, I go through how to use MySQL in Quarkus with Hibernate and Panache.
In the last article, I covered how to build REST APIs using Quarkus. We’ve built a very simple Users
service that has CRUD functionality. For that example, we used SortedSet
as the dummy memory to hold a list of users. Now, it’s time to replace that with the actual database. For that, we will incorporate Hibernate and Panache to build repositories similar to Spring Data.
If you have not looked at Building REST APIs with Quarkus article, I highly recommend you to read that article first before proceeding further.
Vanilla Hibernate is difficult to use
We could use Hibernate solely. But that is overly complicated for such a simple example. And it is not something that most developers are accustomed for. Hence, I figured out having an abstraction layer, similar to Spring Data, is much better suited. That’s why I picked Panache.
Panache vs Spring Data
Quarkus has many useful extensions for various purposes. They are like different Spring frameworks or libraries. There are also many options available to work with databases. The top candidates are:
- Quarkus port of Spring Data JPA
- Panache
I aspired to try something new and opted for Panache. Though, there’s a major difference between Panache and Spring Data. Panache supports both Active Record and Repository Pattern. Thus, it gives more leeway to devs based on their needs.
In this article, we don’t use Active Records however. We stick to the conventional Repository Pattern since it’s a superior pattern and less messy to work with. If you are curious why, read My thoughts on Active record pattern article.
Wiring up Quarkus with Hibernate and Panache
We need to wire a few things up with Hibernate and Panache, to use MySQL in Quarkus.
Let’s start by adding the related dependencies. As we already added Hibernate
and MySQL
driver libraries, we just need to add Panache
dependency.
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql</artifactId>
</dependency>
</dependencies>
For completeness’ sake I have brought Hibernate
and MySQL
dependencies to the above snippet.
Adding MySQL configs to the application.properties file
We should set MySQL details (such as credentials, port and so on) in application.properties
file.
# datasource configuration
quarkus.datasource.db-kind=mysql
quarkus.datasource.username=root
quarkus.datasource.password=secret
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/user_db
# drop and create the database at startup (use `update` to only update the schema)
# quarkus.hibernate-orm.database.generation=update
That does not differ much from any Spring Boot configuration.
Changing the User entity
Since, we already have the User entity, we just need to modify it. Let’s add some annotations. So we can use it with Hibernate.
package com.madadipouya.quarkus.example.entity;
import javax.persistence.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name="first_name", nullable = false)
@NotBlank
@Size(max = 256)
private String firstName;
@Column(name="last_name", nullable = false)
@NotBlank
@Size(max = 256)
private String lastName;
@Column(name="age", nullable = false)
@Min(1)
@Max(200)
private int age;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Creating Panache repository
The next step is to create the UserRepository
. For that, we extendPanacheRepository
.
package com.madadipouya.quarkus.example.repository;
import com.madadipouya.quarkus.example.entity.User;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
}
Adding @ApplicationScoped
allows Quarkus to manage the bean. This is a Javax annotation.
Surprisingly the class has no method. That’s because PanacheRepository
has some base methods that are sufficient for most CRUD operations.
Implementing User service
Implementing UserService
is straightforward. The only remotely challenging part could be how to inject the UserRepository
to the service.
package com.madadipouya.quarkus.example.service;
import com.madadipouya.quarkus.example.entity.User;
import com.madadipouya.quarkus.example.exception.UserNotFoundException;
import java.util.List;
public interface UserService {
User getUserById(long id) throws UserNotFoundException;
List<User> getAllUsers();
User updateUser(long id, User user) throws UserNotFoundException;
User saveUser(User user);
void deleteUser(long id) throws UserNotFoundException;
}
package com.madadipouya.quarkus.example.service.impl;
import com.madadipouya.quarkus.example.entity.User;
import com.madadipouya.quarkus.example.exception.UserNotFoundException;
import com.madadipouya.quarkus.example.repository.UserRepository;
import com.madadipouya.quarkus.example.service.UserService;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.util.List;
@ApplicationScoped
public class DefaultUserService implements UserService {
private final UserRepository userRepository;
@Inject
public DefaultUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User getUserById(long id) throws UserNotFoundException {
return userRepository.findByIdOptional(id).orElseThrow(() -> new UserNotFoundException("There user doesn't exist"));
}
@Override
public List<User> getAllUsers() {
return userRepository.listAll();
}
@Transactional
@Override
public User updateUser(long id, User user) throws UserNotFoundException {
User existingUser = getUserById(id);
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
existingUser.setAge(user.getAge());
userRepository.persist(existingUser);
return existingUser;
}
@Transactional
@Override
public User saveUser(User user) {
userRepository.persistAndFlush(user);
return user;
}
@Transactional
@Override
public void deleteUser(long id) throws UserNotFoundException {
userRepository.delete(getUserById(id));
}
}
As you can see we used @Inject
annotation to inject UserRepository
. The annotation added to the service constructor. Keep in mind that @Inject
is a Javax annotation, don’t mistake it with Google Inject annotation.
Updating UserController
The last step is to update the UserController
and get rid of the dummy set. And replace it with the actual implementation as follows,
package com.madadipouya.quarkus.example.controller;
import com.madadipouya.quarkus.example.entity.User;
import com.madadipouya.quarkus.example.exception.UserNotFoundException;
import com.madadipouya.quarkus.example.service.UserService;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@Path("/v1/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserController {
private final UserService userService;
@Inject
public UserController(UserService userService) {
this.userService = userService;
}
@GET
public List<User> getUsers() {
return userService.getAllUsers();
}
@GET
@Path("/{id}")
public User getUser(@PathParam("id") int id) throws UserNotFoundException {
return userService.getUserById(id);
}
@POST
public User createUser(@Valid UserDto userDto) {
return userService.saveUser(userDto.toUser());
}
@PUT
@Path("/{id}")
public User updateUser(@PathParam("id") int id, @Valid UserDto userDto) throws UserNotFoundException {
return userService.updateUser(id, userDto.toUser());
}
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") int id) throws UserNotFoundException {
userService.deleteUser(id);
return Response.status(Response.Status.NO_CONTENT).build();
}
public static class UserDto {
@NotBlank
private String firstName;
@NotBlank
private String lastName;
@Min(value = 1, message = "The value must be more than 0")
@Max(value = 200, message = "The value must be less than 200")
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User toUser() {
User user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setAge(age);
return user;
}
}
}
Run the project
Let’s run the project,
$ ./mvnw quarkus:dev
To debug the project, run this command,
$ ./mvnw quarkus:dev -Ddebug
That opens port 5005
which then you can connect to it from your IDE.
Complete example
The complete implementation is available on my GitHub at the link below,
https://github.com/kasramp/quarkus-rest-example
Read the readme file for further instruction.
Inline/featured images credits
- Quarkus logo by Quarkus
- MySQL logo on Wikipedia
- Hibernate logo on Wikipedia
- Panache text by Alexandre Magois on Wikipedia (CC BY-SA 4.0)