Use Spring Data R2DBC with Azure Database for PostgreSQL

This article demonstrates creating a sample application that uses Spring Data R2DBC to store and retrieve information in an Azure Database for PostgreSQL database. The sample will use the R2DBC implementation for PostgreSQL from the r2dbc-postgresql repository on GitHub.

R2DBC brings reactive APIs to traditional relational databases. You can use it with Spring WebFlux to create fully reactive Spring Boot applications that use non-blocking APIs. It provides better scalability than the classic "one thread per connection" approach.

Prerequisites

  • An Azure account. If you don't have one, get a free trial.
  • Azure Cloud Shell or Azure CLI 2.37.0 or above required. We recommend Azure Cloud Shell so you'll be logged in automatically and have access to all the tools you'll need.
  • If you're using a Windows machine and want to run the samples locally, install and use the latest Windows Subsystem for Linux (WSL).
  • MySQL command line client. You can connect to your server using a popular client tool, mysql.exe command-line tool with Azure Cloud Shell. Alternatively, you can use mysql command line on your local environment.
  • A supported Java Development Kit, version 8 or above. (17 or above preferred. A JDK is included in Azure Cloud Shell). We recommend installing the Microsoft Build of OpenJDK.
  • Apache's Maven, version 3 or later.
  • A Git client.
  • cURL or a similar HTTP utility to test functionality.

Sample application

In this article, we will code a sample application. If you want to go faster, this application is already coded and available at https://github.com/Azure-Samples/quickstart-spring-data-r2dbc-postgresql.

Prepare the working environment

First, set up some environment variables. In Azure Cloud Shell, run the following commands:

export AZ_RESOURCE_GROUP=database-workshop
export AZ_DATABASE_SERVER_NAME=<YOUR_DATABASE_SERVER_NAME>
export AZ_DATABASE_NAME=<YOUR_DATABASE_NAME>
export AZ_LOCATION=<YOUR_AZURE_REGION>
export AZ_POSTGRESQL_AD_NON_ADMIN_USERNAME=<YOUR_POSTGRESQL_AD_NON_ADMIN_USERNAME>
export AZ_LOCAL_IP_ADDRESS=<YOUR_LOCAL_IP_ADDRESS>
export CURRENT_USERNAME=$(az ad signed-in-user show --query userPrincipalName -o tsv)

Replace the placeholders with the following values, which are used throughout this article:

  • <YOUR_DATABASE_SERVER_NAME>: The name of your PostgreSQL server, which should be unique across Azure.
  • <YOUR_DATABASE_NAME>: The database name of the PostgreSQL server, which should be unique within Azure.
  • <YOUR_AZURE_REGION>: The Azure region you'll use. You can use eastus by default, but we recommend that you configure a region closer to where you live. You can see the full list of available regions by entering az account list-locations.
  • <YOUR_POSTGRESQL_AD_NON_ADMIN_USERNAME>: The username of your PostgreSQL database server. Make sure the username is a valid user in your Azure AD tenant.
  • <YOUR_LOCAL_IP_ADDRESS>: The IP address of your local computer, from which you'll run your Spring Boot application. One convenient way to find it is to open whatismyip.akamai.com.

Important

When setting <YOUR_POSTGRESQL_AD_NON_ADMIN_USERNAME>, the username must already exist in your Azure AD tenant or you will be unable to create an Azure AD user in your database.

Next, create a resource group by using the following command:

az group create \
    --name $AZ_RESOURCE_GROUP \
    --location $AZ_LOCATION \
    --output tsv

Create an Azure Database for PostgreSQL instance

Create a PostgreSQL server and set up admin user

The first thing you'll create is a managed PostgreSQL server with an admin user.

Note

You can read more detailed information about creating PostgreSQL servers in Create an Azure Database for PostgreSQL server by using the Azure portal.

If you're using Azure CLI, run the following command to make sure it has sufficient permission:

az login --scope https://graph.microsoft.com/.default

Then, run following commands to create the server:

az postgres flexible-server create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME \
    --location $AZ_LOCATION \
    --yes \
    --output tsv

Next, to set up an Azure AD administrator after creating the server, follow the steps in Manage Azure Active Directory roles in Azure Database for PostgreSQL - Flexible Server.

Important

When setting up an administrator, a new user with full administrator privileges is added to the PostgreSQL Flexible Server's Azure database. You can create multiple Azure AD administrators per PostgreSQL Flexible Server.

Configure a PostgreSQL database

The PostgreSQL server that you created earlier is empty. Use the following command to create a new database.

az postgres flexible-server db create \
    --resource-group $AZ_RESOURCE_GROUP \
    --database-name $AZ_DATABASE_NAME \
    --server-name $AZ_DATABASE_SERVER_NAME \
    --output tsv

Configure a firewall rule for your PostgreSQL server

Azure Database for PostgreSQL instances are secured by default. They have a firewall that doesn't allow any incoming connection. To be able to use your database, you need to add a firewall rule that will allow the local IP address to access the database server.

Because you configured your local IP address at the beginning of this article, you can open the server's firewall by running the following command:

az postgres flexible-server firewall-rule create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME \
    --rule-name $AZ_DATABASE_SERVER_NAME-database-allow-local-ip \
    --start-ip-address $AZ_LOCAL_IP_ADDRESS \
    --end-ip-address $AZ_LOCAL_IP_ADDRESS \
    --output tsv

If you're connecting to your PostgreSQL server from Windows Subsystem for Linux (WSL) on a Windows computer, you'll need to add the WSL host ID to your firewall.

Obtain the IP address of your host machine by running the following command in WSL:

cat /etc/resolv.conf

Copy the IP address following the term nameserver, then use the following command to set an environment variable for the WSL IP Address:

AZ_WSL_IP_ADDRESS=<the-copied-IP-address>

Then, use the following command to open the server's firewall to your WSL-based app:

az postgres flexible-server firewall-rule create \
    --resource-group $AZ_RESOURCE_GROUP \
    --name $AZ_DATABASE_SERVER_NAME \
    --rule-name $AZ_DATABASE_SERVER_NAME-database-allow-local-ip \
    --start-ip-address $AZ_WSL_IP_ADDRESS \
    --end-ip-address $AZ_WSL_IP_ADDRESS \
    --output tsv

Create a PostgreSQL non-admin user and grant permission

Next, create a non-admin user and grant all permissions to the database.

Note

You can read more detailed information about creating PostgreSQL users in Create users in Azure Database for PostgreSQL.

Create a SQL script called create_ad_user.sql for creating a non-admin user. Add the following contents and save it locally:

cat << EOF > create_ad_user.sql
select * from pgaadauth_create_principal('$AZ_POSTGRESQL_AD_NON_ADMIN_USERNAME', false, false);
EOF

Then, use the following command to run the SQL script to create the Azure AD non-admin user:

psql "host=$AZ_DATABASE_SERVER_NAME.postgres.database.azure.com user=$CURRENT_USERNAME dbname=postgres port=5432 password=$(az account get-access-token --resource-type oss-rdbms --output tsv --query accessToken) sslmode=require" < create_ad_user.sql

Now use the following command to remove the temporary SQL script file:

rm create_ad_user.sql

Create a reactive Spring Boot application

To create a reactive Spring Boot application, we'll use Spring Initializr. The application that we'll create uses:

  • Spring Boot 2.7.6.
  • The following dependencies: Spring Reactive Web (also known as Spring WebFlux) and Spring Data R2DBC.

Generate the application by using Spring Initializr

Generate the application on the command line by using the following command:

curl https://start.spring.io/starter.tgz -d dependencies=webflux,data-r2dbc -d baseDir=azure-database-workshop -d bootVersion=2.7.6 -d javaVersion=17 | tar -xzvf -

Add the reactive PostgreSQL driver implementation

Open the generated project's pom.xml file, and then add the reactive PostgreSQL driver from the r2dbc-postgresql repository on GitHub. After the spring-boot-starter-webflux dependency, add the following text:

<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
    <version>0.8.12.RELEASE</version>
    <scope>runtime</scope>
</dependency>

Configure Spring Boot to use Azure Database for PostgreSQL

Open the src/main/resources/application.properties file, and add the following text:

logging.level.org.springframework.data.r2dbc=DEBUG

spring.r2dbc.url=r2dbc:pool:postgres://$AZ_DATABASE_NAME.postgres.database.azure.com:5432/demo
spring.r2dbc.username=spring@$AZ_DATABASE_NAME
spring.r2dbc.password=$AZ_POSTGRESQL_PASSWORD
spring.r2dbc.properties.sslMode=REQUIRE

Warning

For security reasons, Azure Database for PostgreSQL requires to use SSL connections. This is why you need to add the spring.r2dbc.properties.sslMode=REQUIRE configuration property, otherwise the R2DBC PostgreSQL driver will try to connect using an insecure connection, which will fail.

Replace the two $AZ_DATABASE_NAME variables and the $AZ_POSTGRESQL_PASSWORD variable with the values that you configured at the beginning of this article.

Note

For better performance, the spring.r2dbc.url property is configured to use a connection pool using r2dbc-pool.

You should now be able to start your application by using the provided Maven wrapper as follows:

./mvnw spring-boot:run

Here's a screenshot of the application running for the first time:

The running application

Create the database schema

Inside the main DemoApplication class, configure a new Spring bean that will create a database schema, using the following code:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.r2dbc.connectionfactory.init.ConnectionFactoryInitializer;
import org.springframework.data.r2dbc.connectionfactory.init.ResourceDatabasePopulator;

import io.r2dbc.spi.ConnectionFactory;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
        ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
        initializer.setConnectionFactory(connectionFactory);
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator(new ClassPathResource("schema.sql"));
        initializer.setDatabasePopulator(populator);
        return initializer;
    }
}

This Spring bean uses a file called schema.sql, so create that file in the src/main/resources folder, and add the following text:

DROP TABLE IF EXISTS todo;
CREATE TABLE todo (id SERIAL PRIMARY KEY, description VARCHAR(255), details VARCHAR(4096), done BOOLEAN);

Stop the running application, and start it again using the following command. The application will now use the demo database that you created earlier, and create a todo table inside it.

./mvnw spring-boot:run

Here's a screenshot of the database table as it's being created:

Creation of the database table

Code the application

Next, add the Java code that will use R2DBC to store and retrieve data from your PostgreSQL server.

Create a new Todo Java class, next to the DemoApplication class, using the following code:

package com.example.demo;

import org.springframework.data.annotation.Id;

public class Todo {

    public Todo() {
    }

    public Todo(String description, String details, boolean done) {
        this.description = description;
        this.details = details;
        this.done = done;
    }

    @Id
    private Long id;

    private String description;

    private String details;

    private boolean done;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }
}

This class is a domain model mapped on the todo table that you created before.

To manage that class, you'll need a repository. Define a new TodoRepository interface in the same package, using the following code:

package com.example.demo;

import org.springframework.data.repository.reactive.ReactiveCrudRepository;

public interface TodoRepository extends ReactiveCrudRepository<Todo, Long> {
}

This repository is a reactive repository that Spring Data R2DBC manages.

Finish the application by creating a controller that can store and retrieve data. Implement a TodoController class in the same package, and add the following code:

package com.example.demo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/")
public class TodoController {

    private final TodoRepository todoRepository;

    public TodoController(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @PostMapping("/")
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<Todo> createTodo(@RequestBody Todo todo) {
        return todoRepository.save(todo);
    }

    @GetMapping("/")
    public Flux<Todo> getTodos() {
        return todoRepository.findAll();
    }
}

Finally, halt the application and start it again using the following command:

./mvnw spring-boot:run

Test the application

To test the application, you can use cURL.

First, create a new "todo" item in the database using the following command:

curl --header "Content-Type: application/json" \
    --request POST \
    --data '{"description":"configuration","details":"congratulations, you have set up R2DBC correctly!","done": "true"}' \
    http://127.0.0.1:8080

This command should return the created item, as shown here:

{"id":1,"description":"configuration","details":"congratulations, you have set up R2DBC correctly!","done":true}

Next, retrieve the data by using a new cURL request with the following command:

curl http://127.0.0.1:8080

This command will return the list of "todo" items, including the item you've created, as shown here:

[{"id":1,"description":"configuration","details":"congratulations, you have set up R2DBC correctly!","done":true}]

Here's a screenshot of these cURL requests:

Test with cURL

Congratulations! You've created a fully reactive Spring Boot application that uses R2DBC to store and retrieve data from Azure Database for PostgreSQL.

Clean up resources

To clean up all resources used during this quickstart, delete the resource group using the following command:

az group delete \
    --name $AZ_RESOURCE_GROUP \
    --yes

Next steps

To learn more about deploying a Spring Data application to Azure Spring Apps and using managed identity, see Tutorial: Deploy a Spring application to Azure Spring Apps with a passwordless connection to an Azure database.

To learn more about Spring and Azure, continue to the Spring on Azure documentation center.

Additional resources

For more information about Spring Data R2DBC, see Spring's reference documentation.

For more information about using Azure with Java, see Azure for Java developers and Working with Azure DevOps and Java.