Vaadin进度条在数据库删除操作中无法动态更新的问题排查
ProfilesDeletionPage类负责实现删除操作页面,流程为:上传包含待删除数据的文件→数据存入数据库临时表→异步执行deleteMsisdns方法删除数据→getDeletedCount方法统计删除进度并通过processingUpdated更新ProgressBar。日志显示进度值已正常传递,但进度条无变化,核心问题及修复方案如下:
核心问题1:异步线程中UI.getCurrent()返回null,UI更新代码未执行
在processingUpdated和processingSucceeded方法中,调用UI.getCurrent()获取当前UI实例,但异步线程(由asyncDeletionProcessExecutor执行)未绑定任何UI实例,导致UI.getCurrent()返回null,后续的access()方法不会触发,进度条的setValue操作完全没执行。
修复方案:
在页面初始化时保存当前UI实例,避免在异步线程中动态获取:
// 在ProfilesDeletionPage类中添加成员变量 private UI currentUI; // 构造方法中初始化保存 public ProfilesDeletionPage() { SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); currentUI = UI.getCurrent(); // 保存当前页面的UI实例 // ... 原有其他代码 } // 修改进度更新和完成方法 public void processingUpdated(Float progress) { log.info("processingUpdated"); if (currentUI != null && !currentUI.isClosed()) { currentUI.access(() -> progressBar.setValue(progress)); } } public void processingSucceeded() { log.info("processingSucceeded"); if (currentUI != null && !currentUI.isClosed()) { currentUI.access(() -> { progressBar.setVisible(false); Notification.show("Done!"); }); } }
核心问题2:AsyncDeletionProcessService中deletedCount为全局成员变量,线程不安全
deletedCount是类级别的全局变量,每次调用getDeletedCount时不会重置,若同时有多个删除操作,会导致进度计算混乱;即使单个操作,用户重复点击提交按钮也会出现数据污染。
修复方案:
将deletedCount改为getDeletedCount方法内的局部变量,避免跨线程共享:
public void getDeletedCount(Long totalCountToDelete, Consumer<Float> progressListener, Runnable succeededListener) { Runnable r = () -> { Long deletedCount = 0L; // 改为方法内局部变量 Long remainingCount; while(deletedCount < totalCountToDelete) { try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 正确处理线程中断 return; } remainingCount = dao.getRemainingMsisdnCount(); // 防止数据库查询返回null导致死循环 if (remainingCount == null) break; deletedCount = totalCountToDelete - remainingCount; float progress = (float)deletedCount/totalCountToDelete; log.info("progress {}", progress); progressListener.accept(progress); log.info("remainingCount {}, deletedCount {}", remainingCount, deletedCount); } succeededListener.run(); }; asyncDeletionProcessExecutor.execute(r); }
问题3:两个异步任务无同步,进度计算可能提前结束或无效
deleteMsisdns和getDeletedCount是独立的异步任务,getDeletedCount可能在deleteMsisdns开始执行前就进入循环,此时remainingCount等于totalCountToDelete,deletedCount为0,循环会持续等待;若deleteMsisdns执行速度极快,getDeletedCount可能还没来得及多次查询就直接跳到100%,导致进度条无中间过渡过程。
优化方案:
在删除任务启动后延迟启动进度查询,确保删除操作已开始执行:
submitButton.addClickListener((Button.ClickListener) event -> { final AsyncDeletionProcessService deletionService = app.getBean(AsyncDeletionProcessService.class); progressBar.setCaption("Processing..."); progressBar.setVisible(true); progressBar.setIndeterminate(false); // 启动删除任务 deletionService.deleteMsisdns(sequenceId, user.getId(), user.getLogin()); // 延迟500ms启动进度查询,确保删除操作已初始化 asyncDeletionProcessExecutor.execute(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } deletionService.getDeletedCount(totalCountToDelete, ProfilesDeletionPage.this::processingUpdated, ProfilesDeletionPage.this::processingSucceeded); }); });
附加优化:异常处理不规范问题
Thread.sleep的中断异常仅打印栈轨迹,未正确终止线程;- 文件操作异常仅打印栈轨迹,未给用户反馈。
优化建议:
在UploadListener的异常处理块添加用户提示:
catch(IOException e) { log.error("Failed to process uploaded file", e); Notification.show("文件处理失败:" + e.getMessage(), Notification.Type.ERROR_MESSAGE); }
内容的提问来源于stack exchange,提问作者gonzay

