Java 使用线程发出数据库请求

Java 使用线程发出数据库请求,java,mysql,multithreading,javafx,Java,Mysql,Multithreading,Javafx,我试图理解线程在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.

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

}
包应用;
导入java.sql.ResultSet;
导入java.sql.SQLException;
导入javafx.fxml.fxml;
导入javafx.scene.control.Button;
导入javafx.scene.control.Label;
导入javafx.scene.control.TextField;
公共类控制器{
@FXML
私人按钮getCourseBtn;
@FXML
私有文本字段;
@FXML
自有品牌courseCodeLbl;
专用模型控制器;
私有void requestCourseName(){
字符串courseName=“”;
课程c=新课程();
c、 setCCode(Integer.valueOf(courseId.getText());
mController=新模型控制器(c);
试一试{
ResultSet rs=mController.get();
如果(rs.next()){
courseCodeLbl.setText(rs.getString(1));
}
}捕获(SQLE异常){
//TODO自动生成的捕捉块
e、 printStackTrace();
}
//返回courseName;
}
public void getCourseNameOnClick(){
试一试{
//setText(requestCourseName());
线程t=新线程(新的可运行线程(){
公开募捐{
requestCourseName();
}
}“螺纹A”);
t、 start();
}捕获(数字格式){
//TODO自动生成的捕捉块
e、 printStackTrace();
}
}
}
这将返回一个异常:

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

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

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


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

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

您需要将数据从数据库传递回主UI线程。使用Platform.runLater()计划在主UI线程中运行Runnable

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

或者,您可以。

JavaFX的线程规则

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

  • 任何修改或访问作为场景图一部分的节点状态的代码都必须在JavaFX应用程序线程上执行。某些其他操作(例如创建新的
    Stage
    s)也受此规则约束
  • 任何可能需要很长时间才能运行的代码都应在后台线程上执行(即,不在FX应用程序线程上执行)
  • 第一条规则的原因是,与大多数UI工具包一样,编写框架时不会对场景图元素的状态进行任何同步。添加同步会带来性能成本,而这对于UI工具包来说是一个令人望而却步的成本。因此,只有一个线程可以安全地访问此状态。由于UI线程(适用于JavaFX的FX应用程序线程)需要访问此状态才能渲染场景,因此FX应用程序线程是唯一可以访问“实时”场景图形状态的线程。在JavaFX8和更高版本中,大多数受此规则约束的方法都会执行检查,如果违反此规则,就会抛出运行时异常。(这与Swing相反,Swing可以编写“非法”代码,并且看起来运行良好,但实际上在任意时间都容易出现随机和不可预测的故障。)这就是您看到的
    IllegalStateException
    的原因。
    :您正在调用
    courseCodeLbl.setText(…)
    来自FX应用程序线程以外的线程

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

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

    多线程的一般良好实践

    使用多线程的最佳实践是将要在“用户定义”线程上执行的代码构造为一个对象,该对象使用某个固定状态初始化,具有执行操作的方法,并在完成时返回一个表示结果的对象。对初始化状态和计算结果使用不可变对象是非常可取的。这里的想法是尽可能消除任何可变状态在多个线程中可见的可能性。从数据库访问数据非常符合这个习惯用法:您可以使用数据库访问的参数(搜索词等)初始化“worker”对象。执行数据库查询并获取结果
    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);
        }
    
        // ...
    }
    
    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);
    
    courseCodeLbl.setText(rs.getString(1)); // <--- The culprit
    
    Platform.runLater(() -> courseCodeLbl.setText(rs.getString(1)));
    
    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();
        }
    }