Introduction to Panache

By Thorben Janssen


Panache is a Quarkus-specific library that simplifies the development of your Hibernate-based persistence layer. Similar to Spring Data JPA, Panache handles most of the repetitive boilerplate code for you. Its implementations of the repository and the active record pattern provide methods to create, update, and remove records, perform basic queries, and define and execute your own queries.

Let’s get an overview of Panache’s feature set and a general understanding of its implementation of the repository and the active record pattern. In future articles of this series, we will take a closer look at each pattern and other advanced features.

Project Setup and Dependencies

As explained in my previous article on using Hibernate with Quarkus, the easiest and fastest way to create a Quarkus project is to use the project generator at https://code.quarkus.io/. It provides a comfortable way to select the required dependencies and generates the necessary build files and several Java classes for you.

To use Panache with Hibernate in your project, make sure to add dependencies to quarkus-hibernate-orm-panache and a compatible JDBC driver for your database, e.g., quarkus-jdbc-posgresql. After you did that, you should get a project with the 2 following maven dependencies.

<! – Hibernate ORM with Panache  – >
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>

<! – JDBC driver dependencies – >
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>

Configuration

Panache itself doesn’t require any additional configuration. But you need to configure your database connection, and you can use the properties described in the 1st part of this series to configure Hibernate.

The following configuration connects your application as user postgres to a PostgreSQL database on localhost:5432. It also tells Hibernate to drop and create the database during startup and loads the data.sql script to initialize your database.

# datasource configuration
quarkus.datasource.username = postgres
quarkus.datasource.password = postgres
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/test

# drop and create the database at startup
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=data.sql

You should obviously only use the 2 last configuration properties for demo applications and prototypes. Otherwise, you would drop your production database during every restart. For real applications, I recommend using Flyway or Liquibase to implement a version-based database migration process. Quarkus provides an extension for both of them.

2 Competing Patterns to Implement Your Persistence Layer

As mentioned earlier, Panache implements the repository and the active record pattern. Both implementations provide you with standard methods to persist, read, update and remove entity objects. This article will give you a quick overview of both of them before we dive deeper into each pattern in future articles of this series.

The Repository Pattern

The repository encapsulates the logic to create, read, update and remove an entity object or aggregate as defined by Domain Drive Design. It’s a very popular pattern in the Spring ecosystem, and I explained it in a previous article. Martin Fowler defines a repository as:

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

Repository definition by Martin Fowler

Define your Entities

When using the repository pattern, you can define your entities as standard JPA entities or extend Panache’s PanacheEntity or PanacheEntityBase class. I will use a standard entity in this section and explain Panache’s classes in more detail in the section about the active record pattern.

As defined by the JPA specification, an entity class needs to be a non-final, public class, annotated with @Entity and a default constructor. By default, Hibernate maps this class to a database table with the same. And each attribute gets mapped to a column with the same name. As I show you in my articles on entity mappings, you can customize these mappings using various annotations, and you can, of course, also use them with Panache.

The following code snippet shows an example of a ChessGame entity that gets mapped to the ChessGame table.

@Entity
public class ChessGame {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "game_seq")
    @SequenceGenerator(name = "game_seq", sequenceName = "game_sequence")
    private Long id;

    private LocalDate date;

    private int round;

    @ManyToOne(fetch = FetchType.LAZY)
    private ChessPlayer playerWhite;

    @ManyToOne(fetch = FetchType.LAZY)
    private ChessPlayer playerBlack;

    @Version
    private int version;
	
	...
}

Define a Repository

The only thing you need to do to define a repository is to implement a class that implements the PanacheRepository<Entity> or the PanacheRepositoryBase<Entity, Id> interface. This gets you standard implementations of all methods defined by the interface, and you can add your own methods.

@ApplicationScoped
public class ChessGameRepository implements PanacheRepository<ChessGame> {

    public ChessGame findByRound(int round) {
        return find("round", round).firstResult();
    }
}

Internally, the PanacheRepository extends the PanacheRepositoryBase interface and uses Long as the Id type. The PanacheRepositoryBase interface defines a long list of standard methods to create, read, update, and remove entities. At runtime, Quarkus provides an implementation for each of these methods. Here is a small excerpt of the available methods:

  • void persist(Entity entity) and void persist(Iterable<Entity> entities)
  • void delete(Entity entity) and delete(String query, Parameters params)
  • Entity findById(Id id) and Optional<Entity> findByIdOptional(Id id)
  • List<Entity> list(String query, Object… params)
  • List<Entity> list(String query, Sort sort, Object… params)
  • Stream<Entity> stream(String query, Object… params)
  • long count()
  • long count(String query, Object… params)

For most of the methods listed above, the PanacheRepositoryBase interface defines multiple versions with different input parameters. Please check the interface definition for a complete list.

The repository also provides several methods that you can use to execute your own query, like:

  • PanacheQuery<Entity> find(String query, Object… params)
  • PanacheQuery<Entity> find(String query, Sort sort, Object… params)

We will take a closer look at these methods and other customization options in a future article.

Use a Repository

You can then inject and use your repository to read or write entity objects in your business code.

ChessGame chessGame = new ChessGame();
chessGame.setRound(1);
chessGame.setDate(LocalDate.now());

chessGameRepository.persist(chessGame);

The Active Record Pattern

The activate record pattern is an interesting alternative to the repository pattern. It puts the main focus on the entity object, which implements the methods required to create, read, update and remove a record. Martin Fowler defines this pattern as:

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

Active Record definition by Martin Fowler

Define and Use Entities

Your entity classes need to be annotated with @Entity and extend Panache’s PanacheEntity or PanacheEntityBase class to enable Panache to add its default methods. All entity attributes have to be public, and you don’t need to provide any getter or setter methods. If you need to provide any mapping information, you can annotate each attribute. If you want to provide any custom queries or business logic, you can add a public, static method to your entity class.

As you can see in the following code snippet, this gets you a very concise entity definition.

@Entity
public class ChessPlayer extends PanacheEntity {

    public String firstName;

    public String lastName;

    public LocalDate birthDate;

    @Version
    public int version;

    public static ChessPlayer findByFirstName(String firstName) {
        return find("firstName", firstName).firstResult();
    }
}

In your business code, you can then call the static methods on your entity class to perform the required operation, e.g., to persist a new entity or read one or more entity objects from the database.

ChessPlayer chessPlayer = new ChessPlayer();
chessPlayer.firstName = "Thorben";
chessPlayer.lastName = "Janssen";

chessPlayer.persist();

You can also directly access all fields of your entity objects. Internally, Panache automatically provides getter and setter methods for all fields and rewrites your code to use the methods.

ChessPlayer chessPlayer = ChessPlayer.findByFirstName("Paul");
chessPlayer.firstName = "Peter";

We will take a closer look at all features provided by the active record implementation in future articles.

Conclusion

Panache is a Quarkus-specific library that makes Hibernate much easier to use. The implementations of the repository and the active record pattern handle most of the boilerplate code usually required by a Hibernate-base application and allow you to focus on the business logic.


Tags


About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

Leave a Reply

Your email address will not be published. Required fields are marked

This site uses Akismet to reduce spam. Learn how your comment data is processed.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}