使用线程发出数据库请求 [英] Using threads to make database requests

查看:97
本文介绍了使用线程发出数据库请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试理解线程如何在java中运行。这是一个返回ResultSet的简单数据库请求。我正在使用JavaFx。

I'm trying to understand how threads works in java. This is a simple database request that returns a ResultSet. I'm using JavaFx.

    package application;

import java.sql.ResultSet;
import java.sql.SQLException;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class Controller{
    @FXML
    private Button getCourseBtn;
    @FXML
    private TextField courseId;
    @FXML
    private Label courseCodeLbl;
    private ModelController mController;

    private void requestCourseName(){
        String courseName = "";
        Course c = new Course();
        c.setCCode(Integer.valueOf(courseId.getText()));
        mController = new ModelController(c);
        try {
            ResultSet rs = mController.<Course>get();
            if(rs.next()){
                courseCodeLbl.setText(rs.getString(1));
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
//      return courseName;
    }

    public void getCourseNameOnClick(){
        try {
//              courseCodeLbl.setText(requestCourseName());
            Thread t = new Thread(new Runnable(){
                public void run(){
                    requestCourseName();
                }
            }, "Thread A");
            t.start();
        } catch (NumberFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

此返回例外:


线程中的异常线程Ajava.lang.IllegalStateException:不在FX应用程序线程上; currentThread =线程A

Exception in thread "Thread A" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread A

如何正确实现线程,以便在第二个线程而不是主线程中执行每个数据库请求?

How do I correctly implement threading so that every database request is executed in a second thread instead of the main thread?

我听说过实现Runnable但是如何在run方法中调用不同的方法?

I've heard of implementing Runnable but then how do I invoke different methods in run method?

以前从未使用过线程,但我觉得是时候了。

Never worked with threading before but I thought it's time for it.

推荐答案

JavaFX的线程规则

线程和JavaFX有两个基本规则:

There are two basic rules for threads and JavaFX:


  1. 修改或访问作为场景图的一部分的节点状态的任何代码必须在JavaFX应用程序线程上执行。某些其他操作(例如创建新的 Stage )也受此规则约束。

  2. 任何可能需要很长时间才能执行的代码运行在后台线程上执行(即不在FX应用程序线程上)。

  1. Any code that modifies or accesses the state of a node that is part of a scene graph must be executed on the JavaFX application thread. Certain other operations (e.g. creating new Stages) are also bound by this rule.
  2. Any code that may take a long time to run should be executed on a background thread (i.e. not on the FX Application Thread).

原因对于第一个规则,就像大多数UI工具包一样,框架的编写没有任何同步场景图元素的状态。添加同步会产生性能成本,这对UI工具包来说是一个令人望而却步的成本。因此,只有一个线程可以安全地访问此状态。由于UI线程(用于JavaFX的FX应用程序线程)需要访问此状态以呈现场景,因此FX应用程序线程是您可以访问实时场景图状态的唯一线程。在JavaFX 8及更高版本中,如果违反规则,大多数受此规则约束的方法都会执行检查并抛出运行时异常。 (这与Swing形成鲜明对比,你可以在其中编写非法代码,它可能看起来运行正常,但实际上在任意时间容易出现随机和不可预测的故障。) 这是导致你看到的 IllegalStateException :你正在调用 courseCodeLbl.setText(...)来自FX应用程序线程以外的线程。

The reason for the first rule is that, like most UI toolkits, the framework is written without any synchronization on the state of elements of the scene graph. Adding synchronization incurs a performance cost, and this turns out to be a prohibitive cost for UI toolkits. Thus only one thread can safely access this state. Since the UI thread (FX Application Thread for JavaFX) needs to access this state to render the scene, the FX Application Thread is the only thread on which you can access "live" scene graph state. In JavaFX 8 and later, most methods subject to this rule perform checks and throw runtime exceptions if the rule is violated. (This is in contrast to Swing, where you can write "illegal" code and it may appear to run fine, but is in fact prone to random and unpredictable failure at arbitrary time.) This is the cause of the IllegalStateException you are seeing: you are calling courseCodeLbl.setText(...) from a thread other than the FX Application Thread.

第二条规则的原因是FX应用程序线程以及负责处理用户事件的原因是还负责渲染场景。因此,如果在该线程上执行长时间运行的操作,则在该操作完成之前不会呈现UI,并且将对用户事件无响应。虽然这不会产生异常或导致损坏的对象状态(违反规则1),但它(充其量)会造成糟糕的用户体验。

The reason for the second rule is that the FX Application Thread, as well as being responsible for processing user events, is also responsible for rendering the scene. Thus if you perform a long-running operation on that thread, the UI will not be rendered until that operation is complete, and will become unresponsive to user events. While this won't generate exceptions or cause corrupt object state (as violating rule 1 will), it (at best) creates a poor user experience.

因此,如果你有一个需要在完成时更新UI的长时间运行的操作(如访问数据库),基本计划是在后台线程中执行长时间运行的操作,在完成后返回操作的结果,然后安排UI(FX应用程序)线程上的UI更新。所有单线程UI工具包都有一个机制来执行此操作:在JavaFX中,您可以通过调用 Platform.runLater(Runnable r)来执行 r FX应用程序线程上的.run()。 (在Swing中,您可以调用 SwingUtilities.invokeLater(Runnable r)在AWT上执行 r.run()事件调度线程。)JavaFX(参见本答案后面的内容)还提供了一些更高级别的API来管理返回FX应用程序线程的通信。

Thus if you have a long-running operation (such as accessing a database) that needs to update the UI on completion, the basic plan is to perform the long-running operation in a background thread, returning the results of the operation when it is complete, and then schedule an update to the UI on the UI (FX Application) thread. All single-threaded UI toolkits have a mechanism to do this: in JavaFX you can do so by calling Platform.runLater(Runnable r) to execute r.run() on the FX Application Thread. (In Swing, you can call SwingUtilities.invokeLater(Runnable r) to execute r.run() on the AWT event dispatch thread.) JavaFX (see later in this answer) also provides some higher-level API for managing the communication back to the FX Application Thread.

常规多线程的良好实践

使用多线程的最佳做法是将要在用户定义线程上执行的代码构造为以某种固定状态初始化的对象,具有执行操作的方法,并在完成时返回表示结果的对象。使用不可变对象进行初始化状态和计算结果是非常需要的。这里的想法是消除尽可能从多个线程可见任何可变状态的可能性。从数据库访问数据非常符合这个习惯用法:您可以使用数据库访问的参数(搜索项等)初始化worker对象。执行数据库查询并获取结果集,使用结果集填充域对象的集合,并在结尾返回集合。

The best practice for working with multiple threads is to structure code that is to be executed on a "user-defined" thread as an object that is initialized with some fixed state, has a method to perform the operation, and on completion returns an object representing the result. Using immutable objects for the initialized state and computation result is highly desirable. The idea here is to eliminate the possibility of any mutable state being visible from multiple threads as far as possible. Accessing data from a database fits this idiom nicely: you can initialize your "worker" object with the parameters for the database access (search terms, etc). Perform the database query and get a result set, use the result set to populate a collection of domain objects, and return the collection at the end.

在某些情况下,它将必须在多个线程之间共享可变状态。当必须完成此操作时,您需要仔细同步对该状态的访问,以避免在不一致状态下观察状态(还有其他更微妙的问题需要解决,例如状态的活跃性等)。需要时强烈建议使用高级库来管理这些复杂性。

In some cases it will be necessary to share mutable state between multiple threads. When this absolutely has to be done, you need to carefully synchronize access to that state to avoid observing the state in an inconsistent state (there are other more subtle issues that need to be addressed, such as liveness of the state, etc). The strong recommendation when this is needed is to use a high-level library to manage these complexities for you.

使用javafx.concurrent API

JavaFX提供并发API ,用于在后台线程中执行代码,其API专门用于在执行该代码时(或在执行期间)更新JavaFX UI。此API旨在与 <进行交互code> java.util.concurrent API ,它提供了编写多线程代码的一般工具(但没有UI挂钩)。 javafx.concurrent 中的关键类是 任务 ,表示要在后台线程上执行的单个一次性工作单元。此类定义了一个抽象方法 call(),它不接受任何参数,返回结果,并可能抛出已检查的异常。 任务实现 Runnable 及其 run()方法只需调用()调用任务还有一系列方法可以保证更新FX Application Thread的状态,例如 updateProgress(...) updateMessage(...) 等。它定义了一些可观察的属性(例如 value ):FX应用程序线程的更改将通知这些属性的侦听器。最后,有一些方便的方法来注册处理程序( setOnSucceeded(...) setOnFailed(...) 等);通过这些方法注册的任何处理程序也将在FX应用程序线程上调用。

JavaFX provides a concurrency API that is designed for executing code in a background thread, with API specifically designed for updating the JavaFX UI on completion of (or during) the execution of that code. This API is designed to interact with the java.util.concurrent API, which provides general facilities for writing multithreaded code (but with no UI hooks). The key class in javafx.concurrent is Task, which represents a single, one-off, unit of work intended to be performed on a background thread. This class defines a single abstract method, call(), which takes no parameters, returns a result, and may throw checked exceptions. Task implements Runnable with its run() method simply invoking call(). Task also has a collection of methods which are guaranteed to update state on the FX Application Thread, such as updateProgress(...), updateMessage(...), etc. It defines some observable properties (e.g. state and value): listeners to these properties will be notified of changes on the FX Application Thread. Finally, there are some convenience methods to register handlers (setOnSucceeded(...), setOnFailed(...), etc); any handlers registered via these methods will also be invoked on the FX Application Thread.

因此,从数据库中检索数据的通用公式为:

So the general formula for retrieving data from a database is:


  1. 创建一个任务来处理对数据库的调用。

  2. 使用执行数据库调用所需的任何状态初始化任务

  3. 实现任务的调用( )执行数据库调用的方法,返回调用的结果。

  4. 使用任务注册处理程序,以便在完成后将结果发送到UI 。

  5. 在后台线程上调用任务。

  1. Create a Task to handle the call to the database.
  2. Initialize the Task with any state that is needed to perform the database call.
  3. Implement the task's call() method to perform the database call, returning the results of the call.
  4. Register a handler with the task to send the results to the UI when it is complete.
  5. Invoke the task on a background thread.

对于数据库访问,我强烈建议将实际数据库代码封装在一个对UI一无所知的单独类中(数据访问对象设计模式 )。然后让任务调用数据访问对象上的方法。

For database access, I strongly recommend encapsulating the actual database code in a separate class that knows nothing about the UI (Data Access Object design pattern). Then just have the task invoke the methods on the data access object.

所以你可能有这样的DAO类(注意这里没有UI代码):

So you might have a DAO class like this (note there is no UI code here):

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

检索一堆小部件可能需要很长时间,所以来自UI类(例如控制器类)的任何调用都应该在后台线程上进行调度。控制器类可能如下所示:

Retrieving a bunch of widgets might take a long time, so any calls from a UI class (e.g a controller class) should schedule this on a background thread. A controller class might look like this:

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方法的调用是如何包装在后台线程(通过访问器)上运行的任务中,以防止阻塞UI(上面的规则2)。 UI的更新( widgetTable.setItems(...))实际上是使用任务的便利回调方法 setOnSucceeded(...) (满足规则1)。

Notice how the call to the (potentially) long-running DAO method is wrapped in a Task which is run on a background thread (via the accessor) to prevent blocking the UI (rule 2 above). The update to the UI (widgetTable.setItems(...)) is actually executed back on the FX Application Thread, using the Task's convenience callback method setOnSucceeded(...) (satisfying rule 1).

在您的情况下,您正在执行的数据库访问会返回一个结果,因此您可能有一个方法,如

In your case, the database access you are performing returns a single result, so you might have a method like

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

    // ...
}

然后您的控制器代码看起来像

And then your controller code would look like

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文档还有更多示例,包括更新 progress 任务的属性(对进度条有用......等等。

The API docs for Task have many more examples, including updating the progress property of the task (useful for progress bars..., etc.

这篇关于使用线程发出数据库请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆