Mysql
 sql >> база данни >  >> RDS >> Mysql

Използване на нишки за отправяне на заявки за база данни

Правила за нишки за JavaFX

Има две основни правила за нишките и JavaFX:

  1. Всеки код, който променя или осъществява достъп до състоянието на възел, който е част от графика на сцена трябва да се изпълнява в нишката на приложението JavaFX. Някои други операции (например създаване на нов Stage). s) също са обвързани от това правило.
  2. Всеки код, чието изпълнение може да отнеме много време, трябва да се изпълнява във фонова нишка (т.е. не в нишката на приложението FX).

Причината за първото правило е, че подобно на повечето UI инструменти, рамката е написана без никаква синхронизация на състоянието на елементите на графиката на сцената. Добавянето на синхронизация води до разходи за производителност и това се оказва непосилна цена за UI инструментариите. По този начин само една нишка има безопасен достъп до това състояние. Тъй като UI нишката (FX Application Thread за JavaFX) трябва да получи достъп до това състояние, за да изобрази сцената, FX Application Thread е единствената нишка, в която можете да получите достъп до състоянието на графиката на сцената на живо. В JavaFX 8 и по-нови, повечето методи, предмет на това правило, извършват проверки и хвърлят изключения по време на изпълнение, ако правилото е нарушено. (Това е за разлика от Swing, където можете да пишете „незаконен“ код и може да изглежда, че работи добре, но всъщност е склонен към случаен и непредсказуем отказ в произволно време.) Това е причината за това. IllegalStateException виждаш :вие извиквате courseCodeLbl.setText(...) от нишка, различна от нишката на приложението FX.

Причината за второто правило е, че нишката на приложението FX, освен че отговаря за обработката на потребителски събития, е отговорна и за изобразяването на сцената. По този начин, ако извършите продължителна операция върху тази нишка, потребителският интерфейс няма да бъде изобразен, докато тази операция не приключи и ще престане да реагира на потребителски събития. Въпреки че това няма да генерира изключения или да причини повредено състояние на обекта (както нарушава правило 1), то (в най-добрия случай) създава лошо потребителско изживяване.

По този начин, ако имате продължителна операция (като достъп до база данни), която трябва да актуализира потребителския интерфейс при завършване, основният план е да изпълните продължителната операция във фонова нишка, връщайки резултатите от операцията, когато е завършете и след това насрочете актуализация на потребителския интерфейс в нишката на потребителския интерфейс (FX приложение). Всички еднонишкови UI инструменти имат механизъм за това:в JavaFX можете да го направите, като извикате Platform.runLater(Runnable r) за изпълнение на r.run() в нишката на приложението FX. (В Swing можете да извикате SwingUtilities.invokeLater(Runnable r) за изпълнение на r.run() в нишката за изпращане на събития AWT.) JavaFX (вижте по-нататък в този отговор) също предоставя някои API от по-високо ниво за управление на комуникацията обратно към нишката на приложението FX.

Общи добри практики за многонишковост

Най-добрата практика за работа с множество нишки е да се структурира код, който трябва да се изпълнява върху "дефинирана от потребителя" нишка като обект, който е инициализиран с някакво фиксирано състояние, има метод за извършване на операцията и при завършване връща обект представляващи резултата. Използването на неизменни обекти за инициализираното състояние и резултата от изчислението е много желателно. Идеята тук е да се елиминира възможността всяко променливо състояние да бъде видимо от множество нишки, доколкото е възможно. Достъпът до данни от база данни пасва добре на този идиом:можете да инициализирате вашия обект "работник" с параметрите за достъп до базата данни (термини за търсене и т.н.). Извършете заявката към базата данни и получете набор от резултати, използвайте набора от резултати, за да попълните колекция от обекти на домейна и върнете колекцията в края.

В някои случаи ще е необходимо да се споделя променливо състояние между множество нишки. Когато това абсолютно трябва да се направи, трябва внимателно да синхронизирате достъпа до това състояние, за да избегнете наблюдението на състоянието в непоследователно състояние (има други по-фини проблеми, които трябва да бъдат разгледани, като например жизнеността на състоянието и т.н.). Силната препоръка, когато това е необходимо, е да използвате библиотека от високо ниво, за да управлявате тези сложности вместо вас.

Използване на javafx.concurrent API

JavaFX предоставя API за паралелност който е предназначен за изпълнение на код във фонова нишка, с API, специално проектиран за актуализиране на потребителския интерфейс на JavaFX при завършване (или по време) на изпълнението на този код. Този API е проектиран да взаимодейства с java.util.concurrent API , който предоставя общи средства за писане на многонишков код (но без куки за потребителски интерфейс). Класът на ключовете в javafx.concurrent е Задача , което представлява единична, еднократна единица работа, предназначена да бъде изпълнена върху фонова нишка. Този клас дефинира един абстрактен метод, call() , който не приема параметри, връща резултат и може да хвърля проверени изключения. Задача имплементира Изпълним с неговия run() метод, просто извикващ call() . Задача също има колекция от методи, които гарантирано актуализират състоянието в нишката на FX Application, като updateProgress(...) , updateMessage(...) и т.н. Той дефинира някои наблюдаеми свойства (напр. състояние и стойност ):слушателите на тези свойства ще бъдат уведомени за промени в нишката на приложението FX. И накрая, има някои удобни методи за регистриране на манипулатори (setOnSucceeded(...) , setOnFailed(...) и др.); всички манипулатори, регистрирани чрез тези методи, също ще бъдат извикани в нишката на приложението FX.

Така че общата формула за извличане на данни от база данни е:

  1. Създайте Задача за обработка на извикването към базата данни.
  2. Инициализирайте Задачата с всяко състояние, което е необходимо за извършване на извикването на базата данни.
  3. Приложете call() на задачата метод за извършване на извикването на базата данни, връщайки резултатите от повикването.
  4. Регистрирайте манипулатор със задачата да изпрати резултатите до потребителския интерфейс, когато приключи.
  5. Извикайте задачата във фонова нишка.

За достъп до база данни силно препоръчвам да капсулирате действителния код на базата данни в отделен клас, който не знае нищо за потребителския интерфейс ( Модел за проектиране на обект за достъп до данни ). След това просто накарайте задачата да извика методите на обекта за достъп до данни.

Така че може да имате DAO клас като този (обърнете внимание, че тук няма UI код):

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

Извличането на куп джаджи може да отнеме много време, така че всички повиквания от клас на потребителски интерфейс (например клас на контролер) трябва да планират това във фонова нишка. Класът на контролера може да изглежда така:

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

    // ...
}

Забележете как извикването към (потенциално) дълго работещия метод DAO е обвито в Task който се изпълнява на фонова нишка (чрез инструмента за достъп), за да се предотврати блокирането на потребителския интерфейс (правило 2 по-горе). Актуализацията на потребителския интерфейс (widgetTable.setItems(...) ) всъщност се изпълнява обратно в нишката на FX приложение, като се използва Task удобен метод за обратно извикване на setOnSucceeded(...) (удовлетворяване на правило 1).

Във вашия случай достъпът до базата данни, който извършвате, връща единичен резултат, така че може да имате метод като

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

    // ...
}

И тогава кодът на вашия контролер ще изглежда така

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

API документите за Task има много повече примери, включително актуализиране на progress свойство на задачата (полезно за ленти за напредък... и т.н.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Грешка в MySQL/Writing File (Errcode 28)

  2. Запитване на няколко бази данни наведнъж

  3. Как да инсталирате Adminer в собствено приложение

  4. Как работи операторът SOUNDS LIKE в MySQL

  5. Върнете се към традиционната репликация от GTID