Cykliczne zapytanie do bazy

Autor
Damian
Terlecki
10 minut

Istnieje wiele narzędzi przeznaczonych do monitorowania baz danych i sprawdzania konkretnych warunków. Czasami, programiście (najczęściej w fazie utrzymania), może zajść potrzeba cyklicznego sprawdzania, czy specyficzna sytuacja wystąpiła na bazie. Może to być na przykład wstawienie nowych rekordów, zakończenie długotrwałego zapytania lub po prostu wystąpienie pewnej niespójności w danych, którą należy ręcznie poprawić do czasu wejścia poprawki.

Jeśli nie masz pod ręką żadnego zaawansowanego narzędzia bądź potrzebujesz czegoś prostego i szybkiego, możesz skorzystać z prostego fragmentu kodu napisanego w Javie. Warunkiem wstępnym jego użycia jest dodanie sterownika bazy danych jako zależności. Prezentowany program łączy się z cyklicznie z bazą danych, wysyłając zapytanie do niej sprawdzające pewien warunek, który jeśli jest spełniony, kończy program wyświetleniem okna dialogowego użytkownikowi:

import javax.swing.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Monitor {
    private static final int REPEAT_TIME_MS = 1000 * 60;

    public static void main(String[] args) {
        String query = "SELECT PENDING FROM TEMP_CLEANUP_STATUS WHERE PENDING = 0";
        String connectionUrl = "jdbc:oracle:<drivertype>:<user>/<password>@<database>";
        try {
            //noinspection InfiniteLoopStatement
            while (true) {
                try (Connection connection = DriverManager.getConnection(connectionUrl);
                     Statement statement = connection.createStatement();
                     ResultSet resultSet = statement.executeQuery(query)) {
                    if (!resultSet.next()) {
                        showDialogAndExit(JOptionPane.INFORMATION_MESSAGE, "The condition has been met");
                    }
                }
                Thread.sleep(REPEAT_TIME_MS);
            }
        } catch (Exception e) {
            e.printStackTrace();
            showDialogAndExit(JOptionPane.ERROR_MESSAGE, e.getMessage());
        }
    }

    private static void showDialogAndExit(int optionPaneMessageType, String message) {
        JOptionPane optionPane = new JOptionPane();
        JDialog dialog = optionPane.createDialog("Monitoring");
        optionPane.setMessageType(optionPaneMessageType);
        optionPane.setMessage(message);
        dialog.setAlwaysOnTop(true);
        dialog.setModal(true);
        dialog.setVisible(true);
        System.exit(0);
    }
}

Użyłem tutaj adresu URL właściwego dla sterownika JDBC Oracle, dla innych baz danych (i sterowników) będzie się on nieco różnił. Warto zaznaczyć, że od Javy 1.6 i JDBC API v4.0 ręczne wykrywanie sterownika za pomocą Class.forName('klasa.twojego.sterownika') nie jest już konieczne. Jeśli jednak nadal nie chcesz instalować sterownika, ale masz już zainstalowanego klienta do swojej bazy danych, działającego w trybie wiersza poleceń, równie dobrze możesz wykonać zapytanie za jego pośrednictwem. Wada tego rozwiązania polega na tym, że konieczne jest przefiltrowanie rezultatu wykonania procesu. W przypadku czegoś prostego może się nadać. To wciąż droga naokoło jednak poniżej zamieszczam przykładowy kod:

import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Monitor {
    private static final int REPEAT_TIME_MS = 1000 * 60;
    private static final String SQL_PLUS_PATH = "~/full/path/to/sqlplus.exe";

    public static void main(String[] args) {
        String query = "SELECT PENDING FROM TEMP_CLEANUP_STATUS WHERE PENDING = 0;";
        String connectionUrl = "<user>/<password>@//<host>:<port>/<database>";
        String command = "echo " + query + " | " + SQL_PLUS_PATH + " -L " + connectionUrl;
        try {
            //noinspection InfiniteLoopStatement
            while (true) {
                String[] cmd = new String[]{"cmd.exe", "/C", command};
                Process process = Runtime.getRuntime().exec(cmd);
                String output = readOutput(process);
                if (output.contains("PENDING")) {
                    showDialogAndExit(JOptionPane.INFORMATION_MESSAGE, "The condition has been met");
                }
                Thread.sleep(REPEAT_TIME_MS);
            }
        } catch (Exception e) {
            e.printStackTrace();
            showDialogAndExit(JOptionPane.ERROR_MESSAGE, e.getMessage());
        }
    }

    private static void showDialogAndExit(int optionPaneMessageType, String message) {
        JOptionPane optionPane = new JOptionPane();
        JDialog dialog = optionPane.createDialog("Monitoring");
        optionPane.setMessageType(optionPaneMessageType);
        optionPane.setMessage(message);
        dialog.setAlwaysOnTop(true);
        dialog.setModal(true);
        dialog.setVisible(true);
        System.exit(0);
    }

    private static String readOutput(Process process) throws Exception {
        try (BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
             BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
            StringBuilder errorBuilder = readOutput(errorReader);
            if (errorBuilder.length() != 0) {
                throw new Exception(errorBuilder.toString());
            }
            return readOutput(inputReader).toString();
        }
    }

    private static StringBuilder readOutput(BufferedReader reader) throws IOException {
        StringBuilder sb = new StringBuilder();
        String errorLine;
        while ((errorLine = reader.readLine()) != null) {
            System.out.println(errorLine);
            sb.append(errorLine)
                    .append("\n");
        }
        return sb;
    }
}