Aplikasi Java untuk menyambungkan dan menjalankan perintah SQL di Azure Cosmos DB for PostgreSQL

BERLAKU UNTUK: Azure Cosmos DB for PostgreSQL (didukung oleh ekstensi database Citus ke PostgreSQL)

Mulai cepat ini menunjukkan kepada Anda cara menggunakan kode Java untuk menyambungkan ke kluster, dan menggunakan pernyataan SQL untuk membuat tabel. Anda kemudian akan menyisipkan, mengkueri, memperbarui, dan menghapus data dalam database. Langkah-langkah dalam artikel ini mengasumsikan bahwa Anda terbiasa dengan pengembangan Java dan JDBC, dan baru bekerja dengan Azure Cosmos DB for PostgreSQL.

Menyiapkan proyek dan koneksi Java

Buat proyek Java baru dan file konfigurasi untuk menyambungkan ke Azure Cosmos DB for PostgreSQL.

Membuat proyek Java baru

Menggunakan lingkungan pengembangan terpadu (IDE) favorit Anda, buat proyek Java baru dengan groupId test dan artifactId crud. Di direktori akar proyek, tambahkan file pom.xml dengan konten berikut. File ini mengonfigurasi Apache Maven untuk menggunakan Java 8 dan driver PostgreSQL terbaru untuk 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>

Mengonfigurasi koneksi database

Di src/main/resources/, buat file application.properties dengan konten berikut. Ganti <kluster> dengan nama kluster Anda, dan ganti <kata sandi> dengan kata sandi administratif Anda.

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>

String ?ssl=true&sslmode=require dalam db.url properti memberi tahu driver JDBC untuk menggunakan Keamanan Lapisan Transportasi (TLS) saat menyambungkan ke database. Wajib menggunakan TLS dengan Azure Cosmos DB for PostgreSQL, dan merupakan praktik keamanan yang baik.

Membuat tabel

Mengonfigurasi skema database yang memiliki tabel terdistribusi. Sambungkan ke database untuk membuat skema dan tabel.

Membuat skema database

Di src/main/resources/, buat file schema.sql dengan konten berikut:

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);

Mendistribusikan tabel

Azure Cosmos DB for PostgreSQL memberi Anda kekuatan super dalam mendistribusikan tabel di beberapa simpul untuk skalabilitas. Perintah di bawah ini memungkinkan Anda untuk mendistribusikan tabel. Anda dapat mempelajari selengkapnya tentang create_distributed_table dan kolom distribusi di sini.

Catatan

Mendistribusikan tabel memungkinkan mereka tumbuh di setiap simpul pekerja yang ditambahkan ke kluster.

Untuk mendistribusikan tabel, tambahkan baris berikut ke file schema.sql yang Anda buat di bagian sebelumnya.

select create_distributed_table('public.pharmacy','pharmacy_id');

Menyambungkan ke database dan membuat skema

Selanjutnya, tambahkan kode Java yang menggunakan JDBC untuk menyimpan dan mengambil data dari kluster Anda. Kode ini menggunakan file application.properties dan schema.sql untuk terhubung ke kluster dan membuat skema.

  1. Buat file DButil.java dengan kode berikut, yang berisi DButil kelas . Kelas DBUtil menyiapkan kumpulan koneksi ke PostgreSQL menggunakan HikariCP. Anda menggunakan kelas ini untuk menyambungkan ke PostgreSQL dan mulai mengkueri.

    Tip

    Kode sampel di bawah ini menggunakan kumpulan koneksi untuk membuat dan mengelola koneksi ke PostgreSQL. Pengumpulan koneksi sisi aplikasi sangat disarankan karena:

    • Tindakan ini memastikan bahwa aplikasi tidak menghasilkan terlalu banyak koneksi ke database, sehingga menghindari kelebihan batas koneksi.
    • Tindakan ini dapat membantu meningkatkan performa secara drastis baik latensi maupun throughput. Proses server PostgreSQL harus melakukan fork untuk menangani setiap koneksi baru, dan menggunakan kembali koneksi menghindari overhead tersebut.
    //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;
        }
    }
    
  2. Di src/main/java/, buat file DemoApplication.java yang berisi kode berikut:

    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();
        }
    
    }
    

    Catatan

    Database user dan password kredensial digunakan saat mengeksekusi DriverManager.getConnection(properties.getProperty("url"), properties);. Kredensial disimpan dalam file application.properties , yang diteruskan sebagai argumen.

  3. Anda sekarang dapat menjalankan kelas utama ini dengan alat favorit Anda:

    • Menggunakan IDE Anda, Anda akan dapat mengklik kanan pada kelas DemoApplication dan menjalankannya.
    • Dengan menggunakan Maven, Anda dapat menjalankan aplikasi dengan mengeksekusi:
      mvn exec:java -Dexec.mainClass="com.example.demo.DemoApplication".

Aplikasi harus terhubung ke Azure Cosmos DB for PostgreSQL, membuat skema database, lalu menutup koneksi, seperti yang akan Anda lihat di log konsol:

[INFO   ] Loading application properties
[INFO   ] Connecting to the database
[INFO   ] Database connection test: citus
[INFO   ] Create database schema
[INFO   ] Closing database connection

Membuat kelas domain

Tambahkan kelas Java Pharmacy baru, di samping kelas DemoApplication, dan tambahkan kode berikut:

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 + '\'' +
               '}';
    }
}

Kelas ini adalah model domain yang dipetakan pada tabel Pharmacy yang Anda buat saat menjalankan skrip schema.sql.

Menyisipkan data

Dalam file DemoApplication.java , setelah main metode , tambahkan metode berikut yang menggunakan pernyataan INSERT INTO SQL untuk menyisipkan data ke dalam database:

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();
}

Tambahkan dua baris berikut dalam metode utama:

Pharmacy todo = new Pharmacy(0,"Target","Sunnyvale","California",94001);
insertData(todo, connection);

Mengeksekusi kelas utama sekarang harus menghasilkan output berikut:

[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

Membaca data

Baca data yang sebelumnya Anda sisipkan untuk memvalidasi bahwa kode Anda berfungsi dengan benar.

Dalam file DemoApplication.java , setelah insertData metode , tambahkan metode berikut yang menggunakan pernyataan SELECT SQL untuk membaca data dari database:

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;
}

Tambahkan baris berikut dalam metode utama:

todo = readData(connection);

Mengeksekusi kelas utama sekarang harus menghasilkan output berikut:

[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

Memperbarui data

Perbarui data yang sebelumnya Anda sisipkan.

Masih dalam file DemoApplication.java , setelah readData metode , tambahkan metode berikut untuk memperbarui data di dalam database dengan menggunakan pernyataan UPDATE SQL:

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);
}

Tambahkan dua baris berikut dalam metode utama:

todo.setcity("Guntur");
updateData(todo, connection);

Mengeksekusi kelas utama sekarang harus menghasilkan output berikut:

[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

Menghapus data

Terakhir, hapus data yang sebelumnya Anda sisipkan. Masih dalam file DemoApplication.java , setelah updateData metode , tambahkan metode berikut untuk menghapus data di dalam database dengan menggunakan pernyataan DELETE SQL:

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);
}

Anda sekarang dapat menambahkan baris berikut di dalam metode utama:

deleteData(todo, connection);

Mengeksekusi kelas utama sekarang harus menghasilkan output berikut:

[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

Perintah COPY untuk penyerapan cepat

Perintah COPY dapat menghasilkan throughput yang luar biasa sambil menyerap data ke Azure Cosmos DB for PostgreSQL. Perintah COPY dapat menyerap data dalam file, atau dari batch mikro data dalam memori untuk penyerapan real time.

Perintah COPY untuk memuat data dari file

Kode berikut menyalin data dari file CSV ke tabel database. Sampel kode memerlukan file 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;
}

Anda sekarang dapat menambahkan baris berikut di dalam metode utama:

int c = (int) copyFromFile(connection,"C:\\Users\\pharmacies.csv", "pharmacy");
log.info("Copied "+ c +" rows using COPY command");

Eksekusi kelas main sekarang harus menghasilkan output berikut:

[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

Perintah COPY untuk memuat data dalam memori

Kode berikut menyalin data dalam memori ke tabel.

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);
    }
}

Anda sekarang dapat menambahkan baris berikut di dalam metode utama:

inMemory(connection);

Mengeksekusi kelas utama sekarang harus menghasilkan output berikut:

[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

Aplikasi mencoba kembali untuk kegagalan permintaan database

Terkadang ada kemungkinan permintaan database dari aplikasi Anda gagal. Masalah tersebut dapat terjadi dalam skenario yang berbeda, seperti kegagalan jaringan antara aplikasi dan database, kata sandi yang salah, dll. Beberapa masalah mungkin bersifat sementara, dan selesai sendiri dalam beberapa detik hingga menit. Anda dapat mengonfigurasi logika coba lagi di aplikasi Anda untuk mengatasi kesalahan sementara.

Mengonfigurasi logika coba lagi di aplikasi Anda membantu meningkatkan pengalaman pengguna akhir. Dalam skenario kegagalan, pengguna hanya akan menunggu sedikit lebih lama agar aplikasi melayani permintaan, alih-alih mengalami kesalahan.

Contoh di bawah ini menunjukkan cara menerapkan logika coba lagi di aplikasi Anda. Cuplikan kode sampel mencoba permintaan database setiap 60 detik (hingga lima kali) sampai berhasil. Jumlah dan frekuensi percobaan ulang dapat dikonfigurasi sesuai kebutuhan aplikasi Anda.

Dalam kode ini, ganti <kluster> dengan nama kluster dan <kata sandi> Anda dengan kata sandi administrator Anda.

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);
    }
}

Langkah berikutnya