当前位置: 首页 > 知识库问答 >
问题:

使用线程发出数据库请求

阎承嗣
2023-03-14

我试图理解线程在java中是如何工作的。这是一个返回结果集的简单数据库请求。我正在使用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();
        }
    }

}

这将返回一个异常:

线程“thread A”java中出现异常。lang.IllegalStateException:不在FX应用程序线程上;currentThread=ThreadA

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

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

以前从未使用过线程,但我认为是时候使用它了。

共有3个答案

岳正阳
2023-03-14

这与数据库无关。JavaFx与几乎所有GUI库一样,要求您只使用主UI线程来修改GUI。

您需要将数据库中的数据传回主UI线程。使用Platform.run稍后()来安排Runnable在主UI线程中运行。

public void getCourseNameOnClick(){
    new Thread(new Runnable(){
        public void run(){
            String courseName = requestCourseName();
            Platform.runLater(new Runnable(){
                courseCodeLbl.setText(courseName)
            });
        }
    }, "Thread A").start();
}

或者,您可以使用任务。

潘坚白
2023-03-14

线程“thread A”java中出现异常。lang.IllegalStateException:不在FX应用程序线程上;currentThread=ThreadA

例外情况是,您试图在JavaFX应用程序线程之外访问JavaFX场景图。但在哪里??

courseCodeLbl.setText(rs.getString(1)); // <--- The culprit

如果我不能做到这一点,我该如何使用背景线程?

不同的方法导致了类似的解决方案。

有一种更简单的方法是将上面的行包装在Plaform中。稍后运行,以便在JavaFX应用程序线程上执行。

Platform.runLater(() -> courseCodeLbl.setText(rs.getString(1)));

使用这些场景的更好方法是使用任务,它有专门的方法来发回更新。在下面的示例中,我使用updateMessage来更新消息。此属性绑定到CourseCodeLbltext Property。

Task<Void> task = new Task<Void>() {
    @Override
    public Void call() {
        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()) {
                // update message property
                updateMessage(rs.getString(1));
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

public void getCourseNameOnClick(){
    try {
        Thread t = new Thread(task);
        // To update the label
        courseCodeLbl.textProperty.bind(task.messageProperty());
        t.setDaemon(true); // Imp! missing in your code
        t.start();
    } catch (NumberFormatException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

柳和怡
2023-03-14

JavaFX的线程规则

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

  1. 任何修改或访问作为场景图一部分的节点状态的代码都必须在JavaFX应用程序线程上执行。某些其他操作(例如创建新的阶段)也受此规则的约束
  2. 任何可能需要很长时间才能运行的代码都应该在后台线程上执行(即不在FX应用程序线程上执行)

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

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

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

多线程的一般良好实践

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

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

使用javafx。并发API

JavaFX提供了一个用于在后台线程中执行代码的并发API,该API专门用于在代码执行完成(或期间)更新JavaFX UI。此API旨在与java.util.concurrentAPI交互,它提供了编写多线程代码(但没有UI挂钩)的通用工具。javafx.concurrent中的关键类是Task,它表示打算在后台线程上执行的单个一次性工作单元。此类定义了一个单一的抽象方法call(),它不接受任何参数,返回结果,并且可能会抛出检查过的异常。任务使用其run()方法简单地调用call()来实现Runnable任务还具有保证更新FX应用程序线程上状态的方法集合,例如updategres(...)updateMessage(...)等。它定义了一些可观察的属性(例如statevalue):这些属性的侦听器将被通知FX应用程序线程上的更改。最后,还有一些方便的方法来注册处理程序(setOn成功(...)setOnFail(...)等);通过这些方法注册的任何处理程序也将在FX应用程序线程上调用。

因此,从数据库中检索数据的一般公式是:

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

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

因此,您可能有这样一个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();
    }
}

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

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(widgetable.setItems(…)的更新 (满足规则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文档有更多的示例,包括更新任务的进度属性(对进度条有用…),等等。

 类似资料:
  • 问题内容: 我试图了解线程如何在Java中工作。这是一个简单的数据库请求,返回一个ResultSet。我正在使用JavaFx。 这将返回一个异常: 线程“线程A”中的异常java.lang.IllegalStateException:在FX应用程序线程上不;currentThread =线程A 如何正确实现线程,以便每个数据库请求都在第二个线程而不是主线程中执行? 我听说过要实现Runnable,

  • 问题内容: 我试图了解线程如何在Java中工作。这是一个简单的数据库请求,返回一个ResultSet。我正在使用JavaFx。 这将返回一个异常: 线程“线程A”中的异常java.lang.IllegalStateException:在FX应用程序线程上不;currentThread =线程A 如何正确实现线程,以便每个数据库请求都在第二个线程而不是主线程中执行? 我听说过实现Runnable,但

  • 我正在开发一个web应用程序,需要使用带有post方法的ajax发送一些数据。所以我有两种选择来发送数据,无论是JSON格式还是查询婴儿车。我不知道该用哪个?以JSON格式发送数据安全吗?

  • 问题内容: 我想在其他线程中使用Android Volley库发出请求。 我的意思是,线程中存在连接,UI线程中处理了数据。 我要这样做是因为我有很多连接,要处理的数据很多,现在用户界面被阻止了。 那么,如何在不同的线程中创建和启动连接,然后在UIThread中执行OnResponse()/ OnErrorResponse()? 问题答案: Volley执行的每个网络请求都在后台线程中执行。凌空在

  • 在我们的Java应用程序中,我需要从oracle数据库中读取8000万记录。我试图为此重新设计多线程程序。目前,我们使用Java5个线程池,10个线程基于主键模式并行读取数据库。每个线程将读取不同的模式,如001*和002*。 如何提高该计划的性能?我正在考虑设计模式,让引导线程读取数据库并将处理委托给子线程。在我们现有的设计中,不同的线程通过10个jdbc连接访问表。使用新方法,我将只有一个线程

  • 问题内容: 这可能是一个非常琐碎的问题,但是我很难找到答案: 使用Google Eclipse插件,我想开发一个简单的旧Java应用程序(而不是Web应用程序),该应用程序使用AppEngine进行云存储。 为此,我当然可以简单地创建两个项目,一个包含AppEngine服务器,另一个包含Java应用程序。 但是我想知道是否有可能在Eclipse中设置一个包含服务器和客户端代码的单个项目(例如GWT