Hibernate Reactive – Getting Started Guide


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


If you want to implement a reactive application, you not only need to adapt your way of thinking and switch to reactive libraries for your business layer. You also need to access your database in a reactive way. One way to do that is to use Hibernate Reactive. It’s based on Vert.X and implements the well-known concepts of JPA and Hibernate ORM based on the reactive programming paradigm.

Dependencies and Configuration

Before you can use Hibernate Reactive in your application, you need to add the required dependencies and configure it.

The only 2 dependencies you need are Hibernate Reactive and one of Vert.X reactive database clients. When you add these dependencies, make sure that Hibernate Reactive supports your chosen Vert.X version. Otherwise, you will get some strange exceptions when executing your test cases.

In the examples of this post, I will use a PostgreSQL database and therefore use Vert.X PostgreSQL client.

<project>
	...

	<dependencies>
		<dependency>
			<groupId>org.hibernate.reactive</groupId>
			<artifactId>hibernate-reactive-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>io.vertx</groupId>
			<artifactId>vertx-pg-client</artifactId>
			<version>${vertx.version}</version>
		</dependency>
		...
	</dependencies>
</project>

You can use almost the same persistence.xml configuration for your Hibernate Reactive project as you use in your Hibernate ORM projects. The only difference is that you need to reference org.hibernate.reactive.provider.ReactivePersistenceProvider as your provider.

<persistence>
    <persistence-unit name="my-persistence-unit">
        <description>Hibernate Reactive configuration - thorben-janssen.com</description>
        <provider>org.hibernate.reactive.provider.ReactivePersistenceProvider</provider>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="hibernate.jdbc.time_zone" value="UTC"/>

			<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
			<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test" />
			<property name="javax.persistence.jdbc.user" value="postgres" />
			<property name="javax.persistence.jdbc.password" value="postgres" />
			
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.sql-load-script-source" value="data.sql" />
        </properties>
    </persistence-unit>
</persistence>

Entity Mappings

After you added the required dependencies, you can start working on your entity mappings. As described earlier, Hibernate Reactive is based on Hibernate ORM, which implements the JPA specification. As of now, Hibernate Reactive supports most of Hibernate ORM’s mapping annotations, and you can reference my previous articles about Hibernate and JPA, if you have any questions.

Commonly used annotations that are currently not supported are @ManyToMany and @ElementCollection. But that’s not a huge issue. I recommend avoiding @ElementCollection in general. And instead of a many-to-many association, you can map the association table to its own entity class with 2 many-to-one associations.

All entities need to fulfill the JPA requirements. Here you can see an example of an entity that maps information in the ChessPlayer database table. It maps the first and last name of the player, their date of birth, and 2 associations to the games they played as white and black. The mapping annotations on the id attribute mark it as the primary key attribute and tell Hibernate to use a database sequence to generate unique primary key values.

@Entity
public class ChessPlayer {

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

    private String firstName;
    
    private String lastName;

    private LocalDate birthDate;

    @OneToMany(mappedBy = "playerWhite")
    private Set<ChessGame> gamesWhite;

    @OneToMany(mappedBy = "playerBlack")
    private Set<ChessGame> gamesBlack;

    @Version
    private int version;

    public Long getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    // more getter and setter methods
}

Working with Hibernate Reactive

Based on your entity mappings, you can then implement your database access. Similar to the entity mapping annotations, Hibernate Reactive adapted the SessionFactoy, Session, and Query interfaces. You can decide if you want to use them based on the Mutiny or CompletionStage API. In the following sections, I will provide you an example for both APIs.

Getting a Hibernate SessionFactory

As explained earlier, the configuration is based on JPA, and you can use the standard JPA APIs to get your EntityManagerFactory. In the next step, you need to decide which API you want to use and unwrap a Mutiny.SessionFactory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
SessionFactory factory = emf.unwrap(Mutiny.SessionFactory.class)

or a Stage.SessionFactory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
SessionFactory factory = emf.unwrap(Stage.SessionFactory.class)

In the next step, you can then use the SessionFactory to get a Session and execute your operations. The reactive Session interfaces offer the same methods that you know from Hibernate ORM.

Persisting a New Entity

To persist a new ChessPlayer in your database, you need to instantiate a new ChessPlayer entity object, call the withTransaction method on your SessionFactory to get a Session with an active transaction, and call the persist method. Hibernate Reactive will then flush your new entity and commit the transaction automatically.

Mutiny API

ChessPlayer p = new ChessPlayer();
p.setFirstName("Thorben");
p.setLastName("Janssen");

factory.withTransaction((session, tx) -> session.persist(p))
        .await()
        .indefinitely();

CompletionStage API

ChessPlayer p = new ChessPlayer();
p.setFirstName("Thorben");
p.setLastName("Janssen");

factory.withTransaction((session, tx) -> session.persist(p))
		.toCompletableFuture()
		.join();

Querying Entities

Using Hibernate Reactive, you can query your entities in the same way you do with Hibernate ORM. You can call the find method on your Session to get an entity by its primary key or write queries using JPQL, Criteria API, and native SQL statements.

Mutiny API

factory.withSession(
	session -> session.find(ChessGame.class, 1L)
).await().indefinitely();

CompletionStage API

factory.withSession(
	session -> session.find(ChessGame.class, 1L)
).toCompletableFuture().join();

Initializing Lazy Associations

But there is one important difference. Hibernate Reactive doesn’t transparently fetch lazy associations. You either need to fetch them as part of your query using a JOIN FETCH clause or an EntityGraph, or you need to initialize the association programmatically.

JOIN FETCH Clauses

Let’s use a JOIN FETCH clause first. It’s the recommended approach because it provides better performance than the programmatic initialization and is easier to define than an EntityGraph.

Mutiny API

factory.withSession(session -> session.createQuery("SELECT g FROM ChessGame g LEFT JOIN FETCH g.playerWhite LEFT JOIN FETCH g.playerBlack WHERE g.id = :gameId", ChessGame.class)
									.setParameter("gameId", 1L)
									.getSingleResult()
									.invoke(game -> System.out.println(
															game.getPlayerWhite().getFirstName() + " " + game.getPlayerWhite().getLastName() +
															" played against " +
															game.getPlayerBlack().getFirstName() + " " + game.getPlayerBlack().getLastName()))
).await().indefinitely();

CompletionStage API

factory.withSession(session -> session.createQuery("SELECT g FROM ChessGame g LEFT JOIN FETCH g.playerWhite LEFT JOIN FETCH g.playerBlack WHERE g.id = :gameId", ChessGame.class)
									.setParameter("gameId", 1L)
									.getSingleResult()
									.thenAccept(game -> System.out.println(
															game.getPlayerWhite().getFirstName() + " " + game.getPlayerWhite().getLastName() +
															" played against " +
															game.getPlayerBlack().getFirstName() + " " + game.getPlayerBlack().getLastName()))
).toCompletableFuture().join();

Programmatic Initialization

If you already retrieved an entity from the database and want to fetch an association programmatically, you need to call the Session.fetch method.

Mutiny API

factory.withSession(
	session -> session.find(ChessGame.class, 1L)
						.chain(game -> session.fetch(game.getPlayerWhite())
											  .chain(white -> session.fetch(game.getPlayerBlack()))
											  .invoke(black -> System.out.println(
																game.getPlayerWhite().getFirstName() + " " + game.getPlayerWhite().getLastName() +
																" played against " +
																game.getPlayerBlack().getFirstName() + " " + game.getPlayerBlack().getLastName())))
																					
).await().indefinitely();

CompletionStage API

factory.withSession(session -> session.find(ChessGame.class, 1L)
					.thenCompose(game -> 
						session.fetch(game.getPlayerWhite())
								.thenCompose(white -> session.fetch(game.getPlayerBlack())
								.thenAccept(black -> System.out.println(
												white.getFirstName() + " " + white.getLastName() +
												" played against " +
												black.getFirstName() + " " + black.getLastName()))))
).toCompletableFuture().join();

Updating Entities

The easiest way to update entity objects is to query them from the database and call one or more of their setter methods. When you do that, make sure to call the withTransaction method on your SessionFactory to get a Session instance with an associated transaction. Hibernate Reactive will then handle the required flush and commit operations for you.

Mutiny API

factory.withTransaction((session, tx) -> session.createQuery("SELECT p FROM ChessPlayer p", ChessPlayer.class)
						.getResultList()
						.invoke(players -> players.forEach(player -> player.setFirstName(player.getFirstName().toUpperCase())))
).await().indefinitely();

CompletionStage API

factory.withTransaction((session, tx) -> session.createQuery("SELECT p FROM ChessPlayer p", ChessPlayer.class)
						.getResultList()
						.thenAccept(players -> players.forEach(player -> player.setFirstName(player.getFirstName().toUpperCase())))
).toCompletableFuture().join();

Removing Entities

And you can remove entities in a similar way. You get a Session instance with an associated transaction, query the entities from the database and call the Session.remove method for each of them.

Mutiny API

factory.withTransaction((session, tx) -> session.find(ChessGame.class, 1L)
						.call(game -> session.remove(game))
).await().indefinitely();

CompletionStage API

factory.withTransaction((session, tx) -> session.find(ChessGame.class, 1L)
						.thenAccept(game -> session.remove(game))
).toCompletableFuture().join();

Conclusion

If you’re already familiar with JPA and Hibernate ORM and want to implement a reactive application, then you should try Hibernate Reactive. It enables you to use the same mapping annotations and APIs that you already know from Hibernate ORM.

There are also several differences, like the fetching of lazy associations and the missing support for a few mapping annotations. But these can be easily handled, and they shouldn’t prevent you from giving Hibernate Reactive a try.

2 Comments

  1. Is hibernate-reactive supported by Spring Data as well ?

    1. Avatar photo Thorben Janssen says:

      No, it isn’t. The Spring team has its own library for reactive database access (Spring Data R2DBC) and (as far as I know) has no immediate plans to support another one.

      Regards,
      Thorben

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.