Application Java pour se connecter et exécuter des commandes SQL sur Azure Cosmos DB for PostgreSQL
S’APPLIQUE À : Azure Cosmos DB for PostgreSQL (avec l’extension de base de données Citus pour PostgreSQL)
Ce guide de démarrage rapide vous montre comment utiliser du code Java pour vous connecter à un cluster et utiliser des instructions SQL pour créer une table. Ensuite, vous insérerez, interrogerez, mettrez à jour et supprimerez des données dans la base de données. Cet article suppose que vous connaissez les bases du développement Java et JDBC, et que vous ne savez pas utiliser Azure Cosmos DB for PostgreSQL.
Configurer le projet Java et la connexion
Créez un projet Java et un fichier de configuration pour vous connecter à Azure Cosmos DB for PostgreSQL.
Créer un projet Java
À l’aide de votre environnement de développement intégré (IDE) préféré, créez un projet Java avec groupId test
et artifactId crud
. Dans le répertoire racine du projet, ajoutez un fichier pom.xml avec le contenu suivant. Ce fichier configure Apache Maven pour utiliser Java 8 et un pilote PostgreSQL récent pour Java.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>crud</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
</project>
Configurer la connexion à la base de données
Dans src/main/resources/, créez un fichier application.properties avec le contenu suivant. Remplacez <cluster> par le nom de votre cluster et remplacez <mot de passe> par votre mot de passe administratif ou votre jeton Microsoft Entra ID.
driver.class.name=org.postgresql.Driver
db.url=jdbc:postgresql://c-<cluster>.<uniqueID>.postgres.cosmos.azure.com:5432/citus?ssl=true&sslmode=require
db.username=citus
db.password=<password>
La chaîne ?ssl=true&sslmode=require
de la propriété db.url
indique au pilote JDBC d’utiliser le protocole TLS (Transport Layer Security) lors de la connexion à la base de données. L’utilisation du protocole TLS avec Azure Cosmos DB for PostgreSQL est obligatoire et constitue une pratique de sécurité.
Créer des tables
Configurez un schéma de base de données qui contient des tables distribuées. Connectez-vous à la base de données pour créer le schéma et les tables.
Générer le schéma de base de données
Dans src/main/resources/, créez un fichier schema.sql avec le contenu suivant :
DROP TABLE IF EXISTS public.pharmacy;
CREATE TABLE public.pharmacy(pharmacy_id integer,pharmacy_name text ,city text ,state text ,zip_code integer);
CREATE INDEX idx_pharmacy_id ON public.pharmacy(pharmacy_id);
Distribuer des tables
Azure Cosmos DB for PostgreSQL vous offre la super puissance de distribution de tables sur plusieurs nœuds à des fins de scalabilité. La commande ci-dessous vous permet de distribuer une table. Vous pouvez en savoir plus sur create_distributed_table
et la colonne de distribution ici.
Notes
La distribution de tables leur permet de croître sur tous les nœuds Worker ajoutés au cluster.
Pour distribuer des tables, ajoutez la ligne suivante au fichier schema.sql que vous avez créé dans la section précédente.
select create_distributed_table('public.pharmacy','pharmacy_id');
Se connecter à la base de données et créer le schéma
Ensuite, ajoutez le code Java qui utilise JDBC pour stocker et récupérer des données à partir de votre cluster. Le code ci-dessus utilise les fichiers application.properties et schema.sql pour se connecter au cluster et créer le schéma.
Créez un fichier DButil.java avec le code suivant, qui contient la classe
DButil
. La classeDBUtil
configure un pool de connexions sur PostgreSQL à l’aide de HikariCP. Vous utilisez cette classe pour vous connecter à PostgreSQL et commencer à interroger.Conseil
L’exemple de code ci-dessous utilise un pool de connexions pour créer et gérer des connexions à PostgreSQL. Le regroupement de connexions côté application est fortement recommandé, car :
- Elle garantit que l’application ne génère pas trop de connexions à la base de données, et évite ainsi de dépasser les limites de connexion.
- Cela peut contribuer à améliorer considérablement les performances, à la fois la latence et le débit. Le processus serveur PostgreSQL doit être dupliqué pour gérer chaque nouvelle connexion et réutiliser une connexion, ce qui évite cette surcharge.
//DButil.java package test.crud; import java.io.FileInputStream; import java.io.IOException; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; public class DButil { private static final String DB_USERNAME = "db.username"; private static final String DB_PASSWORD = "db.password"; private static final String DB_URL = "db.url"; private static final String DB_DRIVER_CLASS = "driver.class.name"; private static Properties properties = null; private static HikariDataSource datasource; static { try { properties = new Properties(); properties.load(new FileInputStream("src/main/java/application.properties")); datasource = new HikariDataSource(); datasource.setDriverClassName(properties.getProperty(DB_DRIVER_CLASS )); datasource.setJdbcUrl(properties.getProperty(DB_URL)); datasource.setUsername(properties.getProperty(DB_USERNAME)); datasource.setPassword(properties.getProperty(DB_PASSWORD)); datasource.setMinimumIdle(100); datasource.setMaximumPoolSize(1000000000); datasource.setAutoCommit(true); datasource.setLoginTimeout(3); } catch (IOException | SQLException e) { e.printStackTrace(); } } public static DataSource getDataSource() { return datasource; } }
Dans src/main/java/, créez un fichier DemoApplication.java qui contient le code suivant :
package test.crud; import java.io.IOException; import java.sql.*; import java.util.*; import java.util.logging.Logger; import java.io.FileInputStream; import java.io.FileOutputStream; import org.postgresql.copy.CopyManager; import org.postgresql.core.BaseConnection; import java.io.IOException; import java.io.Reader; import java.io.StringReader; public class DemoApplication { private static final Logger log; static { System.setProperty("java.util.logging.SimpleFormatter.format", "[%4$-7s] %5$s %n"); log =Logger.getLogger(DemoApplication.class.getName()); } public static void main(String[] args)throws Exception { log.info("Connecting to the database"); Connection connection = DButil.getDataSource().getConnection(); System.out.println("The Connection Object is of Class: " + connection.getClass()); log.info("Database connection test: " + connection.getCatalog()); log.info("Creating table"); log.info("Creating index"); log.info("distributing table"); Scanner scanner = new Scanner(DemoApplication.class.getClassLoader().getResourceAsStream("schema.sql")); Statement statement = connection.createStatement(); while (scanner.hasNextLine()) { statement.execute(scanner.nextLine()); } log.info("Closing database connection"); connection.close(); } }
Notes
Les informations d’identification
user
etpassword
de base de données sont utilisées lors de l’exécution deDriverManager.getConnection(properties.getProperty("url"), properties);
. Les informations d’identification sont stockées dans le fichier application.properties, qui est transmis en tant qu’argument.Vous pouvez maintenant exécuter cette classe main avec votre outil favori :
- Avec votre IDE, vous devez être en mesure de cliquer avec le bouton droit sur la classe
DemoApplication
et de l’exécuter. - À l’aide de Maven, vous pouvez utiliser l’application en exécutant :
mvn exec:java -Dexec.mainClass="com.example.demo.DemoApplication"
.
- Avec votre IDE, vous devez être en mesure de cliquer avec le bouton droit sur la classe
L’application doit se connecter à Azure Cosmos DB for PostgreSQL, créer un schéma de base de données, puis fermer la connexion, comme vous le verrez dans les journaux de la console :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Create database schema
[INFO ] Closing database connection
Créer une classe de domaine
Créez une classe Java Pharmacy
parallèlement à la classe DemoApplication
, puis ajoutez le code suivant :
public class Pharmacy {
private Integer pharmacy_id;
private String pharmacy_name;
private String city;
private String state;
private Integer zip_code;
public Pharmacy() { }
public Pharmacy(Integer pharmacy_id, String pharmacy_name, String city,String state,Integer zip_code)
{
this.pharmacy_id = pharmacy_id;
this.pharmacy_name = pharmacy_name;
this.city = city;
this.state = state;
this.zip_code = zip_code;
}
public Integer getpharmacy_id() {
return pharmacy_id;
}
public void setpharmacy_id(Integer pharmacy_id) {
this.pharmacy_id = pharmacy_id;
}
public String getpharmacy_name() {
return pharmacy_name;
}
public void setpharmacy_name(String pharmacy_name) {
this.pharmacy_name = pharmacy_name;
}
public String getcity() {
return city;
}
public void setcity(String city) {
this.city = city;
}
public String getstate() {
return state;
}
public void setstate(String state) {
this.state = state;
}
public Integer getzip_code() {
return zip_code;
}
public void setzip_code(Integer zip_code) {
this.zip_code = zip_code;
}
@Override
public String toString() {
return "TPharmacy{" +
"pharmacy_id=" + pharmacy_id +
", pharmacy_name='" + pharmacy_name + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zip_code='" + zip_code + '\'' +
'}';
}
}
Cette classe est un modèle de domaine mappé sur la table Pharmacy
que vous avez créée lors de l’exécution du script schema.sql.
Insertion des données
Dans le fichier DemoApplication.java, après la méthode main
, ajoutez la méthode suivante qui utilise l’instruction SQL INSERT INTO pour insérer des données dans la base de données :
private static void insertData(Pharmacy todo, Connection connection) throws SQLException {
log.info("Insert data");
PreparedStatement insertStatement = connection
.prepareStatement("INSERT INTO pharmacy (pharmacy_id,pharmacy_name,city,state,zip_code) VALUES (?, ?, ?, ?, ?);");
insertStatement.setInt(1, todo.getpharmacy_id());
insertStatement.setString(2, todo.getpharmacy_name());
insertStatement.setString(3, todo.getcity());
insertStatement.setString(4, todo.getstate());
insertStatement.setInt(5, todo.getzip_code());
insertStatement.executeUpdate();
}
Ajoutez les deux lignes suivantes à la méthode main :
Pharmacy todo = new Pharmacy(0,"Target","Sunnyvale","California",94001);
insertData(todo, connection);
L’exécution de la classe main doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Closing database connection
Lire les données
Lisez les données que vous avez précédemment insérées pour valider le bon fonctionnement de votre code.
Dans le fichier DemoApplication.java, après la méthode insertData
, ajoutez la méthode suivante qui utilise l’instruction SQL SELECT pour lire des données à partir de la base de données :
private static Pharmacy readData(Connection connection) throws SQLException {
log.info("Read data");
PreparedStatement readStatement = connection.prepareStatement("SELECT * FROM Pharmacy;");
ResultSet resultSet = readStatement.executeQuery();
if (!resultSet.next()) {
log.info("There is no data in the database!");
return null;
}
Pharmacy todo = new Pharmacy();
todo.setpharmacy_id(resultSet.getInt("pharmacy_id"));
todo.setpharmacy_name(resultSet.getString("pharmacy_name"));
todo.setcity(resultSet.getString("city"));
todo.setstate(resultSet.getString("state"));
todo.setzip_code(resultSet.getInt("zip_code"));
log.info("Data read from the database: " + todo.toString());
return todo;
}
Ajoutez la ligne suivante à la méthode main :
todo = readData(connection);
L’exécution de la classe main doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Sunnyvale', state='California', zip_code='94001'}
[INFO ] Closing database connection
Mettre à jour des données
Mettez à jour les données que vous avez insérées précédemment.
Toujours dans le fichier DemoApplication.java, après la méthode readData
, ajoutez la méthode suivante pour mettre à jour des données dans la base de données en utilisant l’instruction SQL UPDATE :
private static void updateData(Pharmacy todo, Connection connection) throws SQLException {
log.info("Update data");
PreparedStatement updateStatement = connection
.prepareStatement("UPDATE pharmacy SET city = ? WHERE pharmacy_id = ?;");
updateStatement.setString(1, todo.getcity());
updateStatement.setInt(2, todo.getpharmacy_id());
updateStatement.executeUpdate();
readData(connection);
}
Ajoutez les deux lignes suivantes à la méthode main :
todo.setcity("Guntur");
updateData(todo, connection);
L’exécution de la classe main doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Sunnyvale', state='California', zip_code='94001'}
[INFO ] Update data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Guntur', state='California', zip_code='94001'}
[INFO ] Closing database connection
Suppression de données
Enfin, supprimez les données que vous avez insérées précédemment. Toujours dans le fichier DemoApplication.java, après la méthode updateData
, ajoutez la méthode suivante pour supprimer des données dans la base de données en utilisant l’instruction SQL DELETE :
private static void deleteData(Pharmacy todo, Connection connection) throws SQLException {
log.info("Delete data");
PreparedStatement deleteStatement = connection.prepareStatement("DELETE FROM pharmacy WHERE pharmacy_id = ?;");
deleteStatement.setLong(1, todo.getpharmacy_id());
deleteStatement.executeUpdate();
readData(connection);
}
Vous pouvez maintenant ajouter la ligne suivante dans la méthode principale :
deleteData(todo, connection);
L’exécution de la classe main doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Sunnyvale', state='California', zip_code='94001'}
[INFO ] Update data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Guntur', state='California', zip_code='94001'}
[INFO ] Delete data
[INFO ] Read data
[INFO ] There is no data in the database!
[INFO ] Closing database connection
Commande COPY pour l’ingestion rapide
La commande COPY peut générer un débit considérable lors de l’ingestion de données dans Azure Cosmos DB for PostgreSQL. La commande COPY peut ingérer des données dans des fichiers ou à partir de micro-lots de données en mémoire pour l’ingestion en temps réel.
Commande COPY pour charger des données à partir d’un fichier
Le code suivant copie des données à partir d’un fichier CSV dans une table de base de données. L’exemple de code nécessite le fichier pharmacies.csv.
public static long
copyFromFile(Connection connection, String filePath, String tableName)
throws SQLException, IOException {
long count = 0;
FileInputStream fileInputStream = null;
try {
Connection unwrap = connection.unwrap(Connection.class);
BaseConnection connSec = (BaseConnection) unwrap;
CopyManager copyManager = new CopyManager((BaseConnection) connSec);
fileInputStream = new FileInputStream(filePath);
count = copyManager.copyIn("COPY " + tableName + " FROM STDIN delimiter ',' csv", fileInputStream);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return count;
}
Vous pouvez maintenant ajouter la ligne suivante dans la méthode principale :
int c = (int) copyFromFile(connection,"C:\\Users\\pharmacies.csv", "pharmacy");
log.info("Copied "+ c +" rows using COPY command");
L’exécution de la classe main
doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Sunnyvale', state='California', zip_code='94001'}
[INFO ] Update data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Guntur', state='California', zip_code='94001'}
[INFO ] Delete data
[INFO ] Read data
[INFO ] There is no data in the database!
[INFO ] Copied 5000 rows using COPY command
[INFO ] Closing database connection
Commande COPY pour charger des données en mémoire
Le code suivant copie des données en mémoire dans une table.
private static void inMemory(Connection connection) throws SQLException,IOException
{
log.info("Copying inmemory data into table");
final List<String> rows = new ArrayList<>();
rows.add("0,Target,Sunnyvale,California,94001");
rows.add("1,Apollo,Guntur,Andhra,94003");
final BaseConnection baseConnection = (BaseConnection) connection.unwrap(Connection.class);
final CopyManager copyManager = new CopyManager(baseConnection);
// COPY command can change based on the format of rows. This COPY command is for above rows.
final String copyCommand = "COPY pharmacy FROM STDIN with csv";
try (final Reader reader = new StringReader(String.join("\n", rows))) {
copyManager.copyIn(copyCommand, reader);
}
}
Vous pouvez maintenant ajouter la ligne suivante dans la méthode principale :
inMemory(connection);
L’exécution de la classe main doit maintenant produire la sortie suivante :
[INFO ] Loading application properties
[INFO ] Connecting to the database
[INFO ] Database connection test: citus
[INFO ] Creating table
[INFO ] Creating index
[INFO ] distributing table
[INFO ] Insert data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Sunnyvale', state='California', zip_code='94001'}
[INFO ] Update data
[INFO ] Read data
[INFO ] Data read from the database: Pharmacy{pharmacy_id=0, pharmacy_name='Target', city='Guntur', state='California', zip_code='94001'}
[INFO ] Delete data
[INFO ] Read data
[INFO ] There is no data in the database!
5000
[INFO ] Copying in-memory data into table
[INFO ] Closing database connection
Nouvelle tentative d’application pour les échecs de requête de base de données
Il arrive parfois que les requêtes de base de données en provenance de votre application échouent. Ces problèmes peuvent se produire dans différents scénarios, comme une défaillance réseau entre une application et une base de données, un mot de passe incorrect, etc. Certains problèmes peuvent être temporaires et se résoudre d’eux-mêmes en quelques secondes ou quelques minutes. Vous pouvez configurer la logique de nouvelle tentative dans votre application pour surmonter les erreurs temporaires.
La configuration de la logique de nouvelle tentative dans votre application permet d’améliorer l’expérience de l’utilisateur final. Dans les scénarios de défaillance, les utilisateurs attendent simplement un peu plus longtemps que l’application traite les requêtes, au lieu d’être confrontés à des erreurs.
L’exemple ci-dessous montre comment implémenter une logique de nouvelle tentative dans votre application. L’exemple d’extrait de code tente une requête de base de données toutes les 60 secondes (jusqu’à cinq fois) jusqu’à ce qu’elle réussisse. Le nombre et la fréquence des nouvelles tentatives peuvent être configurés en fonction des besoins de votre application.
Dans ce code, remplacez <cluster> par le nom de votre cluster et <mot de passe> par votre mot de passe d’administration.
package test.crud;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.logging.Logger;
import com.zaxxer.hikari.HikariDataSource;
public class DemoApplication
{
private static final Logger log;
static
{
System.setProperty("java.util.logging.SimpleFormatter.format", "[%4$-7s] %5$s %n");
log = Logger.getLogger(DemoApplication.class.getName());
}
private static final String DB_USERNAME = "citus";
private static final String DB_PASSWORD = "<password>";
private static final String DB_URL = "jdbc:postgresql://c-<cluster>.<uniqueID>.postgres.cosmos.azure.com:5432/citus?sslmode=require";
private static final String DB_DRIVER_CLASS = "org.postgresql.Driver";
private static HikariDataSource datasource;
private static String executeRetry(String sql, int retryCount) throws InterruptedException
{
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
for (int i = 1; i <= retryCount; i++)
{
try
{
datasource = new HikariDataSource();
datasource.setDriverClassName(DB_DRIVER_CLASS);
datasource.setJdbcUrl(DB_URL);
datasource.setUsername(DB_USERNAME);
datasource.setPassword(DB_PASSWORD);
datasource.setMinimumIdle(10);
datasource.setMaximumPoolSize(1000);
datasource.setAutoCommit(true);
datasource.setLoginTimeout(3);
log.info("Connecting to the database");
con = datasource.getConnection();
log.info("Connection established");
log.info("Read data");
pst = con.prepareStatement(sql);
rs = pst.executeQuery();
StringBuilder builder = new StringBuilder();
int columnCount = rs.getMetaData().getColumnCount();
while (rs.next())
{
for (int j = 0; j < columnCount;)
{
builder.append(rs.getString(j + 1));
if (++j < columnCount)
builder.append(",");
}
builder.append("\r\n");
}
return builder.toString();
}
catch (Exception e)
{
Thread.sleep(60000);
System.out.println(e.getMessage());
}
}
return null;
}
public static void main(String[] args) throws Exception
{
String result = executeRetry("select 1", 5);
System.out.print(result);
}
}
Étapes suivantes
- Découvrir comment l’API Azure Cosmos DB for PostgreSQL étend PostgreSQL et essayer des requêtes de diagnostic utiles
- Choisir la meilleure taille de cluster pour votre charge de travail
- Surveiller les performances du cluster