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

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

问题描述

我试图了解线程在 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();
        }
    }

}

这会返回一个异常:

线程线程 A"中的异常 java.lang.IllegalStateException:不在 FX 应用程序线程上;当前线程 = 线程 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 应用程序线程上执行.某些其他操作(例如创建新的 Stages)也受此规则约束.
  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:您正在从 FX 应用程序线程以外的线程调用 courseCodeLbl.setText(...).

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 Application Thread,除了负责处理用户事件,还负责渲染场景.因此,如果您在该线程上执行长时间运行的操作,则在该操作完成之前不会呈现 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.run()FX 应用程序线程.(在 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 旨在与 java.util.concurrent API,提供用于编写​​多线程代码的通用工具(但没有 UI 挂钩).javafx.concurrent 中的关键类是 Task,表示旨在在后台线程上执行的单个一次性工作单元.这个类定义了一个抽象方法,call(),它不带参数,返回一个结果,并可能抛出已检查的异常.Task 实现了 Runnable 及其 run() 方法,只需调用 call().Task 还有一组方法可以保证更新 FX 应用线程上的状态,例如 updateProgress(...), updateMessage(...) 等.它定义了一些可观察的属性(例如 statevalue):这些属性的侦听器将收到有关 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. 创建一个Task来处理对数据库的调用.
  2. 使用执行数据库调用所需的任何状态初始化 Task.
  3. 实现任务的call()方法来执行数据库调用,返回调用结果.
  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 方法的调用是如何包装在一个 Task 中的,它在后台线程上运行(通过访问器)以防止阻塞 UI(上面的规则 2)).UI的更新(widgetTable.setItems(...))实际上是在FX应用线程上执行的,使用Task的便利回调方法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);

Task 的 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天全站免登陆