我正在尝试使用针对某些数据库异常的重试策略来实现数据库查询。重试策略的代码不是很相关,所以我没有包含它。正如您在下面的代码中看到的那样 - 我编写了一个retryCallable,它采用了重试策略和populateData()
中的Callable。
在getDataFromDB
中,我从数据库中获取数据,并将数据放在一个全局散列图中,该散列图充当应用程序级别的缓存。
此代码按预期工作。我想从另一个班级调用populateData
。但是,这将是一个阻止呼叫。由于这是数据库并且具有重试策略,因此这可能很慢。我想异步调用populateData
。
如何使用CompletableFuture或FutureTask来实现这一目标?
CompletableFuture.runAsync
期待一个可运行的。 CompletableFuture.supplyAsync
期待供应商。我以前没有实现过这些东西。所以关于最佳实践的任何建议都会有所帮助。
Class TestCallableRetry {
public void populateData() {
final Callable<Set<String>> retryCallable = new RetryingCallable<>(retryStrategyToRetryOnDBException(), getDataFromDB());
Set<String> data = new HashSet<>();
data = retryCallable.call();
if (data != null && !data.isEmpty()) {
// store data in a global hash map
}
}
private Callable<Set<Building>> getDataFromDB() {
return new Callable<Set<String>>() {
@Override
public Set<String> call() {
// returns data from database
}
};
}
}
Class InvokeCallableAsynchronously {
public void putDataInGlobalMap {
// call populateData asynchronously
}
}
答案 0 :(得分:2)
您在CompletableFuture
中结合了多种实用方法,并且值得探索所有这些方法。
让我们从populateData
方法开始。根据其名称,您可以推断它应该接受来自某个地方的数据流。
它的签名可能如下所示:
void populateData ( Supplier<? extends Collection<Building> dataSupplier );
Supplier
,顾名思义,它只是为我们提供了一些数据。
getDataFromDB()
似乎适合担任Supplier
角色。
private Set<Building> getDataFromDB() // supply a building's collection
我们希望populateData
执行asynchronously
并返回结果,无论操作是否正确执行。
因此,将来populateData
可能会返回,并告诉我们事情的进展情况。
让我们将签名转换为:
CompletableFuture<Result> populateData(Supplier<? extends Collection<Building>> supplier);
现在让我们看一下方法体的外观:
CompletableFuture<Result> populateData(Supplier<? extends Collection<Building>> supplier) {
return CompletableFuture // create new completable future from factory method
.supplyAsync(supplier) // execute the supplier method (getDataFromDB() in our case)
.thenApplyAsync(data -> { // here we can work on the data supplied
if (data == null || data.isEmpty()) return new Result(false);
// some heavy operations
for (Building building : data) {
// do something
}
return new Result(true); // return dummy positive result data
})
.handleAsync((result, throwable) -> {
// check if there was any exception
if (throwable != null) {
// check if exception was thrown
Log.log(throwable);
return new Result(false);
}
return result;
});
}
现在我们可以从某处调用populateData
,并在异步完成执行时应用另一个回调来执行。
populateData(TestCallableRetry::getDataFromDB).thenAccept( result -> {
if ( ! result.success ) {
// things went bad... retry ??
}
});
现在,这取决于您希望如何应用重试策略。如果您只想重试一次,则可以在populateData
内再次拨打thenAcceptAsync
。
您还应该在供应商方法中catch
例外,并将其转换为java.util.concurrent.CompletionException
,因为它们会在CompletableFuture
内顺利处理。
答案 1 :(得分:1)
如果您将populateData
方法拆分为两个部分,一个Supplier
用于获取数据,另一个Consumer
用于存储数据,将很容易用{{1}链接它们}}
CompletableFuture
然后,在// Signature compatible with Supplier<Set<String>>
private Set<String> fetchDataWithRetry() {
final RetryingCallable<Set<String>> retryCallable = new RetryingCallable<>(retryStrategyToRetryOnDBException(), getDataFromDB());
try {
return retryCallable.call();
} catch (Exception e) {
log.error("Call to database failed", e);
return Collections.emptySet();
}
}
// Signature compatible with Consumer<Set<String>>
private void storeData(Set<String> data) {
if (!data.isEmpty()) {
// store data in a global hash map
}
}
:
populateData()
使用private ExecutorService executor = Executors.newCachedThreadPool();
public void populateData() {
CompletableFuture
.supplyAsync(this::fetchDataWithRetry, executor)
.thenAccept(this::storeData);
}
版本supplyAsync
是可选的。如果您使用单个arg版本,您的任务将在公共池中运行;对于短期运行任务可以,但对于阻止的任务则不行。
答案 2 :(得分:0)
它非常简单,因为java8只是使用
CompletableFuture.runAsync(() -> object.func());