java 使用线程进行数据库请求
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30249493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using threads to make database requests
提问by Mnemonics
I'm trying to understand how threads works in java. This is a simple database request that returns a ResultSet. I'm using JavaFx.
我试图了解线程在 Java 中是如何工作的。这是一个简单的数据库请求,它返回一个 ResultSet。我正在使用 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();
}
}
}
This returns an exception:
这将返回一个异常:
Exception in thread "Thread A" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread A
线程“线程 A”中的异常 java.lang.IllegalStateException:不在 FX 应用程序线程上;当前线程 = 线程 A
How do I correctly implement threading so that every database request is executed in a second thread instead of the main thread?
我如何正确实现线程以便每个数据库请求都在第二个线程而不是主线程中执行?
I've heard of implementing Runnable but then how do I invoke different methods in run method?
我听说过实现 Runnable 但是我如何在 run 方法中调用不同的方法?
Never worked with threading before but I thought it's time for it.
以前从未使用过线程,但我认为是时候了。
回答by James_D
Threading Rules for JavaFX
JavaFX 的线程规则
There are two basic rules for threads and JavaFX:
线程和 JavaFX 有两个基本规则:
- Any code that modifies or accesses the state of a node that is part of a scene graph mustbe executed on the JavaFX application thread. Certain other operations (e.g. creating new
Stage
s) are also bound by this rule. - Any code that may take a long time to run shouldbe executed on a background thread (i.e. not on the FX Application Thread).
- 任何修改或访问作为场景图一部分的节点状态的代码都必须在 JavaFX 应用程序线程上执行。某些其他操作(例如创建新
Stage
的)也受此规则约束。 - 任何可能需要很长时间运行的代码都应该在后台线程上执行(即不在 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.
第一条规则的原因是,像大多数 UI 工具包一样,框架的编写没有对场景图元素的状态进行任何同步。添加同步会导致性能成本,而这对于 UI 工具包来说是一个令人望而却步的成本。因此只有一个线程可以安全地访问此状态。由于 UI 线程(JavaFX 的 FX 应用线程)需要访问此状态来渲染场景,因此 FX 应用线程是您可以访问“实时”场景图状态的唯一线程。在 JavaFX 8 及更高版本中,大多数受此规则约束的方法会执行检查并在违反规则时抛出运行时异常。(这与 Swing 形成对比,在那里您可以编写“非法”代码并且它可能看起来运行良好,但实际上在任意时间容易出现随机和不可预测的故障。)这就是IllegalStateException
您所看到的原因:您courseCodeLbl.setText(...)
从 FX 应用程序线程以外的线程调用。
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.
第二条规则的原因是 FX 应用线程,除了负责处理用户事件,还负责渲染场景。因此,如果您在该线程上执行长时间运行的操作,则在该操作完成之前不会呈现 UI,并且将对用户事件无响应。虽然这不会产生异常或导致损坏的对象状态(如违反规则 1 那样),但它(充其量)会造成糟糕的用户体验。
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.
因此,如果您有一个需要在完成时更新 UI 的长时间运行的操作(例如访问数据库),基本计划是在后台线程中执行长时间运行的操作,并在执行时返回操作的结果完成,然后在 UI(FX 应用程序)线程上安排对 UI 的更新。所有单线程 UI 工具包都有一个机制来做到这一点:在 JavaFX 中,您可以通过调用FX 应用程序线程Platform.runLater(Runnable r)
来执行r.run()
。(在 Swing 中,您可以调用SwingUtilities.invokeLater(Runnable r)
以r.run()
在 AWT 事件分派线程上执行。)JavaFX(见本答案后面部分)还提供了一些更高级别的 API,用于管理返回 FX 应用程序线程的通信。
General Good Practices for Multithreading
多线程的一般良好实践
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.
使用多线程的最佳实践是将要在“用户定义”线程上执行的代码结构化为一个对象,该对象以某种固定状态初始化,具有执行操作的方法,并在完成时返回一个对象代表结果。对于初始化状态和计算结果使用不可变对象是非常可取的。这里的想法是尽可能消除从多个线程可见的任何可变状态的可能性。从数据库访问数据非常适合这个习惯用法:您可以使用数据库访问参数(搜索词等)初始化“worker”对象。执行数据库查询并获取结果集,使用结果集填充域对象集合,并在最后返回该集合。
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.
在某些情况下,需要在多个线程之间共享可变状态。当绝对必须这样做时,您需要小心地同步对该状态的访问,以避免在不一致的状态下观察状态(还有其他更微妙的问题需要解决,例如状态的活跃度等)。强烈建议在需要时使用高级库来为您管理这些复杂性。
Using the javafx.concurrent API
使用 javafx.concurrent API
JavaFX provides a concurrency APIthat 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.
JavaFX 提供了一个并发 API,该API旨在用于在后台线程中执行代码,该 API 专门用于在该代码执行完成(或期间)更新 JavaFX UI。此 API 旨在与java.util.concurrent
API交互,后者提供用于编写多线程代码的通用工具(但没有 UI 挂钩)。中的关键类javafx.concurrent
是Task
,它表示旨在在后台线程上执行的单个一次性工作单元。这个类定义了一个抽象方法,call()
,它不带参数,返回一个结果,并可能抛出已检查的异常。用它的方法Task
实现简单地调用.Runnable
run()
call()
Task
还具有被保证更新状态的FX应用程序线程的方法,例如作为一个集合updateProgress(...)
,updateMessage(...)
等,这定义了一些可观察到的性质(例如state
和value
):听众对这些属性将被通知的FX应用程序线程的变化。最后,有一些方便的方法来注册处理程序(setOnSucceeded(...)
,setOnFailed(...)
等); 通过这些方法注册的任何处理程序也将在 FX 应用程序线程上调用。
So the general formula for retrieving data from a database is:
所以从数据库中检索数据的一般公式是:
- Create a
Task
to handle the call to the database. - Initialize the
Task
with any state that is needed to perform the database call. - Implement the task's
call()
method to perform the database call, returning the results of the call. - Register a handler with the task to send the results to the UI when it is complete.
- Invoke the task on a background thread.
- 创建一个
Task
来处理对数据库的调用。 - 使用
Task
执行数据库调用所需的任何状态初始化。 - 实现任务的
call()
方法来执行数据库调用,返回调用的结果。 - 向任务注册一个处理程序,以便在完成时将结果发送到 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.
对于数据库访问,我强烈建议将实际的数据库代码封装在一个对 UI 一无所知的单独类中(数据访问对象设计模式)。然后让任务调用数据访问对象上的方法。
So you might have a DAO class like this (note there is no UI code here):
所以你可能有一个这样的 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();
}
}
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:
检索一堆小部件可能需要很长时间,因此来自 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);
}
// ...
}
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).
请注意对(可能)长时间运行的 DAO 方法的调用如何封装在Task
在后台线程(通过访问器)上运行的 a 中,以防止阻塞 UI(上面的规则 2)。对 UI ( widgetTable.setItems(...)
)的更新实际上是在 FX 应用程序线程上执行的,使用Task
的便捷回调方法setOnSucceeded(...)
(满足规则 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);
The API docs for Task
have many more examples, including updating the progress
property of the task (useful for progress bars..., etc.
该API文档Task
有更多的例子,包括更新progress
任务的属性进度条...等(有用
回答by ItachiUchiha
Exception in thread "Thread A" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread A
线程“线程 A”中的异常 java.lang.IllegalStateException:不在 FX 应用程序线程上;当前线程 = 线程 A
The exception is trying to tell you that you are trying to access JavaFX scene graph outside the JavaFX application thread. But where ??
异常是试图告诉您您正在尝试在 JavaFX 应用程序线程之外访问 JavaFX 场景图。但是哪里 ??
courseCodeLbl.setText(rs.getString(1)); // <--- The culprit
If I can't do this how do I use a background thread?
如果我不能这样做,我该如何使用后台线程?
The are different approaches which leads to similar solutions.
不同的方法导致类似的解决方案。
Wrap you Scene graph element with Platform.runLater
用 Platform.runLater 包裹你的场景图元素
There easier and most simple way is to wrap the above line in Plaform.runLater
, such that it gets executed on JavaFX Application thread.
有更简单和最简单的方法是将上面的行包装在 中Plaform.runLater
,这样它就可以在 JavaFX 应用程序线程上执行。
Platform.runLater(() -> courseCodeLbl.setText(rs.getString(1)));
Use Task
使用任务
The better approachto go with these scenarios is to use Task, which has specialized methods to send back updates. In the following example, I am using updateMessage
to update the message. This property is bind to courseCodeLbl
textProperty.
处理这些场景的更好方法是使用Task,它具有专门的方法来发回更新。在以下示例中,我使用updateMessage
更新消息。此属性绑定到courseCodeLbl
textProperty。
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();
}
}
回答by Lie Ryan
This has nothing to do with database. JavaFx, like pretty much all GUI libraries, requires that you only use the main UI thread to modify the GUI.
这与数据库无关。JavaFx 与几乎所有 GUI 库一样,要求您仅使用主 UI 线程来修改 GUI。
You need to pass the data from the database back to the main UI thread. Use Platform.runLater() to schedule a Runnable to be run in the main UI thread.
您需要将数据从数据库传递回主 UI 线程。使用 Platform.runLater() 安排一个 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();
}
Alternatively, you can use Task.
或者,您可以使用 Task。