图片批量管理
...大约 2 分钟云图库项目
类似以下效果:

代码如下:
开发批量修改图片服务,依次完成参数校验、空间权限校验、图片查询、批量更新操作:4AeqD6wp8ZemHEHZXii0QseGHx9Ks4t5/a/SqKw4Xao=
// 1. 查询指定图片,仅选择需要的字段
List<Picture> pictureList = this.lambdaQuery()
.select(Picture::getId, Picture::getSpaceId)
.eq(Picture::getSpaceId, spaceId)
.in(Picture::getId, pictureIdList)
.list();
if (pictureList.isEmpty()) {
return;
}
// 2. 更新分类和标签
pictureList.forEach(picture -> {
if (StrUtil.isNotBlank(category)) {
picture.setCategory(category);
}
if (CollUtil.isNotEmpty(tags)) {
picture.setTags(JSONUtil.toJsonStr(tags));
}
});
// 3. 批量更新
boolean result = this.updateBatchById(pictureList);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
对这个项目来说,用户要处理的数据量不大,上述代码已经能够满足需求。但如果要处理大量数据,可以使用线程池 + 分批 + 并发进行优化,参考代码如下:
@Resource
private ThreadPoolExecutor customExecutor;
/**
* 批量编辑图片分类和标签
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void batchEditPictureMetadata(PictureBatchEditRequest request, Long spaceId, Long loginUserId) {
// 参数校验
validateBatchEditRequest(request, spaceId, loginUserId);
// 查询空间下的图片
List<Picture> pictureList = this.lambdaQuery()
.eq(Picture::getSpaceId, spaceId)
.in(Picture::getId, request.getPictureIds())
.list();
if (pictureList.isEmpty()) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "指定的图片不存在或不属于该空间");
}
// 分批处理避免长事务
int batchSize = 100;
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < pictureList.size(); i += batchSize) {
List<Picture> batch = pictureList.subList(i, Math.min(i + batchSize, pictureList.size()));
// 异步处理每批数据
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
batch.forEach(picture -> {
// 编辑分类和标签
if (request.getCategory() != null) {
picture.setCategory(request.getCategory());
}
if (request.getTags() != null) {
picture.setTags(String.join(",", request.getTags()));
}
});
boolean result = this.updateBatchById(batch);
if (!result) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
}
}, customExecutor);
futures.add(future);
}
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
1. 分批处理避免长事务
batchSize = 100
:每批次处理 100 条数据。pictureList
:需要处理的图片列表。subList
:将pictureList
分成多个小批次,每批次最多包含batchSize
条数据。
2. 异步处理每批数据
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
batch.forEach(picture -> {
// 编辑分类和标签
if (request.getCategory() != null) {
picture.setCategory(request.getCategory());
}
if (request.getTags() != null) {
picture.setTags(String.join(",", request.getTags()));
}
});
boolean result = this.updateBatchById(batch);
if (!result) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
}
}, customExecutor);
CompletableFuture.runAsync
:异步执行任务。batch.forEach
:对每批次中的每张图片进行处理:- 如果请求中有分类信息(
request.getCategory()
),则更新图片的分类。 - 如果请求中有标签信息(
request.getTags()
),则将标签拼接成字符串并更新图片的标签。
- 如果请求中有分类信息(
updateBatchById(batch)
:批量更新数据库中的图片数据。BusinessException
:如果更新失败,抛出业务异常。
3. 将异步任务加入列表
java
复制
futures.add(future);
- 将每个批次的异步任务(
CompletableFuture
)加入futures
列表,方便后续统一管理。
4. 等待所有任务完成
java
复制
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
CompletableFuture.allOf
:等待所有异步任务完成。join()
:阻塞当前线程,直到所有任务执行完毕。
5. 线程池配置
java
复制
customExecutor
customExecutor
:自定义的线程池,用于控制异步任务的并发度。- 如果没有显式传入线程池,
CompletableFuture
会使用默认的ForkJoinPool
。