Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Używanie wątków do wysyłania żądań do bazy danych

Zasady wątków dla JavaFX

Istnieją dwie podstawowe zasady dotyczące wątków i JavaFX:

  1. Każdy kod, który modyfikuje lub uzyskuje dostęp do stanu węzła, który jest częścią wykresu sceny musi być wykonywane w wątku aplikacji JavaFX. Niektóre inne operacje (np. tworzenie nowego Stage s) są również związane tą zasadą.
  2. Każdy kod, którego uruchomienie może zająć dużo czasu, powinien być wykonywane w wątku w tle (tzn. nie w wątku aplikacji FX).

Powodem pierwszej reguły jest to, że podobnie jak większość zestawów narzędzi UI, framework jest napisany bez synchronizacji stanu elementów grafu sceny. Dodanie synchronizacji wiąże się z kosztem wydajności, a to okazuje się być zaporowym kosztem zestawów narzędzi interfejsu użytkownika. W ten sposób tylko jeden wątek może bezpiecznie uzyskać dostęp do tego stanu. Ponieważ wątek interfejsu użytkownika (Wątek aplikacji FX dla JavaFX) musi uzyskać dostęp do tego stanu, aby renderować scenę, wątek aplikacji FX jest jedynym wątkiem, w którym można uzyskać dostęp do stanu wykresu sceny „na żywo”. W JavaFX 8 i nowszych większość metod podlegających tej regule wykonuje sprawdzenia i zgłasza wyjątki czasu wykonywania, jeśli reguła zostanie naruszona. (W przeciwieństwie do Swinga, w którym można napisać „nielegalny” kod i może wydawać się, że działa dobrze, ale w rzeczywistości jest podatny na przypadkowe i nieprzewidywalne awarie w dowolnym momencie.) To jest przyczyna IllegalStateException widzisz :dzwonisz courseCodeLbl.setText(...) z wątku innego niż wątek aplikacji FX.

Powodem drugiej zasady jest to, że wątek aplikacji FX, oprócz tego, że jest odpowiedzialny za przetwarzanie zdarzeń użytkownika, jest również odpowiedzialny za renderowanie sceny. W związku z tym, jeśli wykonasz długotrwałą operację w tym wątku, interfejs użytkownika nie będzie renderowany, dopóki ta operacja nie zostanie zakończona i przestanie reagować na zdarzenia użytkownika. Chociaż nie wygeneruje to wyjątków ani nie spowoduje uszkodzenia stanu obiektu (co spowoduje naruszenie zasady 1), (w najlepszym przypadku) spowoduje to złe wrażenia użytkownika.

Tak więc, jeśli masz długotrwałą operację (np. dostęp do bazy danych), która wymaga aktualizacji interfejsu użytkownika po zakończeniu, podstawowym planem jest wykonanie długotrwałej operacji w wątku w tle, zwracając wyniki operacji, gdy jest zakończyć, a następnie zaplanować aktualizację interfejsu użytkownika w wątku interfejsu użytkownika (aplikacji FX). Wszystkie jednowątkowe zestawy narzędzi UI mają do tego mechanizm:w JavaFX możesz to zrobić, wywołując Platform.runLater(Runnable r) wykonać r.run() w wątku aplikacji FX. (W Swingu możesz wywołać SwingUtilities.invokeLater(Runnable r) wykonać r.run() w wątku wysyłania zdarzeń AWT.) JavaFX (patrz dalej w tej odpowiedzi) zapewnia również interfejs API wyższego poziomu do zarządzania komunikacją z powrotem do wątku aplikacji FX.

Ogólne dobre praktyki dotyczące wielowątkowości

Najlepszą praktyką podczas pracy z wieloma wątkami jest struktura kodu, który ma być wykonywany na wątku „zdefiniowanym przez użytkownika”, jako obiekt, który jest inicjowany z pewnym ustalonym stanem, ma metodę do wykonania operacji, a po zakończeniu zwraca obiekt reprezentujący wynik. Używanie niezmiennych obiektów dla stanu inicjalizacji i wyniku obliczeń jest wysoce pożądane. Pomysł polega na tym, aby w miarę możliwości wyeliminować możliwość, że jakikolwiek mutowalny stan będzie widoczny z wielu wątków. Dostęp do danych z bazy danych dobrze pasuje do tego idiomu:możesz zainicjować swój obiekt "pracownik" z parametrami dostępu do bazy danych (wyszukiwane terminy itp.). Wykonaj zapytanie do bazy danych i uzyskaj zestaw wyników, użyj zestawu wyników do wypełnienia kolekcji obiektów domeny i zwróć kolekcję na końcu.

W niektórych przypadkach konieczne będzie współdzielenie zmiennego stanu przez wiele wątków. Kiedy jest to absolutnie konieczne, musisz ostrożnie zsynchronizować dostęp do tego stanu, aby uniknąć obserwowania stanu w niespójnym stanie (istnieją inne bardziej subtelne kwestie, które należy rozwiązać, takie jak żywotność stanu itp.). Silną rekomendacją, gdy jest to potrzebne, jest użycie biblioteki wysokiego poziomu do zarządzania tymi złożonościami za Ciebie.

Korzystanie z interfejsu API javafx.concurrent

JavaFX zapewnia interfejs API współbieżności który jest przeznaczony do wykonywania kodu w wątku w tle, z interfejsem API zaprojektowanym specjalnie do aktualizowania interfejsu użytkownika JavaFX po zakończeniu (lub w trakcie) wykonywania tego kodu. Ten interfejs API jest przeznaczony do interakcji z java.util.concurrent API , który zapewnia ogólne udogodnienia do pisania kodu wielowątkowego (ale bez zaczepów interfejsu użytkownika). Klasa klucza w javafx.concurrent to Zadanie , który reprezentuje pojedynczą, jednorazową jednostkę pracy, która ma zostać wykonana w wątku w tle. Ta klasa definiuje pojedynczą metodę abstrakcyjną, call() , który nie przyjmuje parametrów, zwraca wynik i może zgłaszać sprawdzone wyjątki. Zadanie implementuje Runnable z jego run() metoda po prostu wywołująca call() . Zadanie ma również zbiór metod, które gwarantują aktualizację stanu w wątku aplikacji efektów, takich jak updateProgress(...) , updateMessage(...) itp. Definiuje pewne obserwowalne właściwości (np. stan i wartość ):słuchacze tych właściwości zostaną powiadomieni o zmianach w wątku aplikacji FX. Wreszcie istnieje kilka wygodnych metod rejestrowania programów obsługi (setOnSucceeded(...) , setOnFailed(...) itp.); wszelkie programy obsługi zarejestrowane za pomocą tych metod będą również wywoływane w wątku aplikacji FX.

Ogólny wzór na pobieranie danych z bazy danych to:

  1. Utwórz zadanie do obsługi połączenia do bazy danych.
  2. Zainicjuj zadanie z dowolnym stanem wymaganym do wykonania wywołania bazy danych.
  3. Zaimplementuj funkcję call() zadania metoda do wykonania wywołania bazy danych, zwracając wyniki wywołania.
  4. Zarejestruj program obsługi z zadaniem, aby wysłać wyniki do interfejsu użytkownika po jego zakończeniu.
  5. Wywołaj zadanie w wątku w tle.

W przypadku dostępu do bazy danych zdecydowanie zalecam umieszczenie rzeczywistego kodu bazy danych w oddzielnej klasie, która nie wie nic o interfejsie użytkownika ( Wzorzec projektowy obiektu dostępu do danych ). Następnie wystarczy, że zadanie wywoła metody w obiekcie dostępu do danych.

Możesz więc mieć taką klasę DAO (zauważ, że nie ma tu kodu interfejsu użytkownika):

public class WidgetDAO {

    // In real life, you might want a connection pool here, though for
    // desktop applications a single connection often suffices:
    private Connection conn ;

    public WidgetDAO() throws Exception {
        conn = ... ; // initialize connection (or connection pool...)
    }

    public List<Widget> getWidgetsByType(String type) throws SQLException {
        try (PreparedStatement pstmt = conn.prepareStatement("select * from widget where type = ?")) {
            pstmt.setString(1, type);
            ResultSet rs = pstmt.executeQuery();
            List<Widget> widgets = new ArrayList<>();
            while (rs.next()) {
                Widget widget = new Widget();
                widget.setName(rs.getString("name"));
                widget.setNumberOfBigRedButtons(rs.getString("btnCount"));
                // ...
                widgets.add(widget);
            }
            return widgets ;
        }
    }

    // ...

    public void shutdown() throws Exception {
        conn.close();
    }
}

Pobieranie wielu widżetów może zająć dużo czasu, więc wszelkie wywołania z klasy interfejsu użytkownika (np. klasy kontrolera) powinny zaplanować to w wątku w tle. Klasa kontrolera może wyglądać tak:

public class MyController {

    private WidgetDAO widgetAccessor ;

    // java.util.concurrent.Executor typically provides a pool of threads...
    private Executor exec ;

    @FXML
    private TextField widgetTypeSearchField ;

    @FXML
    private TableView<Widget> widgetTable ;

    public void initialize() throws Exception {
        widgetAccessor = new WidgetDAO();

        // create executor that uses daemon threads:
        exec = Executors.newCachedThreadPool(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t ;
        });
    }

    // handle search button:
    @FXML
    public void searchWidgets() {
        final String searchString = widgetTypeSearchField.getText();
        Task<List<Widget>> widgetSearchTask = new Task<List<Widget>>() {
            @Override
            public List<Widget> call() throws Exception {
                return widgetAccessor.getWidgetsByType(searchString);
            }
        };

        widgetSearchTask.setOnFailed(e -> {
           widgetSearchTask.getException().printStackTrace();
            // inform user of error...
        });

        widgetSearchTask.setOnSucceeded(e -> 
            // Task.getValue() gives the value returned from call()...
            widgetTable.getItems().setAll(widgetSearchTask.getValue()));

        // run the task using a thread from the thread pool:
        exec.execute(widgetSearchTask);
    }

    // ...
}

Zwróć uwagę, jak wywołanie (potencjalnie) długotrwałej metody DAO jest opakowane w Zadanie który jest uruchamiany w wątku w tle (za pośrednictwem akcesora), aby zapobiec blokowaniu interfejsu użytkownika (zasada 2 powyżej). Aktualizacja interfejsu użytkownika (widgetTable.setItems(...) ) jest faktycznie wykonywany z powrotem w wątku aplikacji efektów za pomocą Zadania wygodna metoda wywołania zwrotnego setOnSucceeded(...) (spełniająca reguła 1).

W twoim przypadku dostęp do bazy danych, który wykonujesz, zwraca pojedynczy wynik, więc możesz mieć metodę taką jak

public class MyDAO {

    private Connection conn ; 

    // constructor etc...

    public Course getCourseByCode(int code) throws SQLException {
        try (PreparedStatement pstmt = conn.prepareStatement("select * from course where c_code = ?")) {
            pstmt.setInt(1, code);
            ResultSet results = pstmt.executeQuery();
            if (results.next()) {
                Course course = new Course();
                course.setName(results.getString("c_name"));
                // etc...
                return course ;
            } else {
                // maybe throw an exception if you want to insist course with given code exists
                // or consider using Optional<Course>...
                return null ;
            }
        }
    }

    // ...
}

A wtedy twój kod kontrolera wyglądałby tak

final int courseCode = Integer.valueOf(courseId.getText());
Task<Course> courseTask = new Task<Course>() {
    @Override
    public Course call() throws Exception {
        return myDAO.getCourseByCode(courseCode);
    }
};
courseTask.setOnSucceeded(e -> {
    Course course = courseTask.getCourse();
    if (course != null) {
        courseCodeLbl.setText(course.getName());
    }
});
exec.execute(courseTask);

dokumentacja API dla zadania mają o wiele więcej przykładów, w tym aktualizowanie postępu właściwość zadania (przydatna w przypadku pasków postępu... itd.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wywołanie funkcji składowej execute() na wartości logicznej in

  2. Zresetuj hasło roota MySQL w systemie Windows

  3. SQL DELETE z JOIN inną tabelą dla warunku WHERE

  4. 4 sposoby na zastąpienie wartości NULL inną wartością w MySQL

  5. BIN() — pobierz wartość binarną liczby w MySQL