java 线程 数据库_Java 使用线程发出数据库请求

本文介绍了JavaFX中线程的基本规则,强调了长时间运行的代码应在后台线程执行,以防止阻塞UI。同时,推荐使用javafx.concurrent API的Task类来执行后台任务并在完成后更新UI。通过示例展示了如何在后台线程中执行数据库查询,并在FX Application Thread上安全地更新UI。
摘要由CSDN通过智能技术生成

小编典典

JavaFX的线程规则

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

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

任何可能花费很长时间运行的代码都应在后台线程上执行(即不在FX Application线程上执行)。

第一条规则的原因是,像大多数UI工具包一样,编写框架时对场景图的元素状态没有任何同步。添加同步会导致性能损失,这对于UI工具包来说是一笔过高的费用。因此,只有一个线程可以安全地访问此状态。由于UI线程(JavaFX的FX应用程序线程)需要访问此状态才能呈现场景,因此FX Application线程是唯一可以访问“实时”场景图状态的线程。在JavaFX 8和更高版本中,受此规则约束的大多数方法都会执行检查,并在违反该规则时抛出运行时异常。(这与Swing相反,在Swing中,你可以编写“非法”代码,并且看起来可能运行良好,但实际上在任意时间都容易出现随机且无法预测的故障。)这是IllegalStateException你看到的原因:你是courseCodeLbl.setText(...)从FX Application Thread之外的其他线程进行调用。

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

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

多线程通用良好实践

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

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

使用javafx.concurrent API

JavaFX提供了一个并发API,专门用于在后台线程中执行代码,而该API专门用于在代码执行完成时(或执行期间)更新JavaFX UI。此API旨在与java.util.concurrentAPI进行交互,该API提供了用于编写多线程代码的常规工具(但没有UI挂钩)。中的键类javafx.concurrent是Task,它表示打算在后台线程上执行的单个一次性工作单元。此类定义了一个抽象方法,call()该方法不带任何参数,返回结果并可能引发检查的异常。用调用的方法Task实现。Runnablerun()call()Task还具有被保证更新状态的FX应用程序线程的方法,例如作为一个集合updateProgress(...),updateMessage(...)等,这定义了一些可观察到的性质(例如state和value):听众对这些属性将被通知的FX应用程序线程的变化。最后,有一些方便的方法来注册处理程序(setOnSucceeded(...),setOnFailed(...)等); 通过这些方法注册的所有处理程序也将在FX Application Thread上调用。

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

创建一个Task来处理对数据库的调用。

使用Task执行数据库调用所需的任何状态初始化。

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

向任务注册一个处理程序,以在完成后将结果发送到UI。

在后台线程上调用任务。

对于数据库访问,我强烈建议将实际的数据库代码封装在一个对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 getWidgetsByType(String type) throws SQLException {

try (PreparedStatement pstmt = conn.prepareStatement("select * from widget where type = ?")) {

pstmt.setString(1, type);

ResultSet rs = pstmt.executeQuery();

List 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 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> widgetSearchTask = new Task>() {

@Override

public List 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)。widgetTable.setItems(...)实际上,使用Task的便捷回调方法setOnSucceeded(...)(满足规则1)在FX Application线程上执行了对UI()的更新。

在你的情况下,你正在执行的数据库访问将返回单个结果,因此你可能具有类似以下的方法

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...

return null ;

}

}

}

// ...

}

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

final int courseCode = Integer.valueOf(courseId.getText());

Task courseTask = new Task() {

@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文档Task有更多的例子,包括更新progress任务的属性进度条…等(有用

2020-02-27

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值