Specification
实战) Hello,各位在代码世界中追求数据管理极致与 API (Application Programming Interface, 应用程序编程接口) 设计优雅的工匠们! 在任何一个国际化或涉及多种计价单位的系统中,币种管理都是一项基础而关键的功能。今天,我们将深入剖析一个功能强大的后端接口——findCurrencies
,它允许管理员或系统查询币种列表,并支持精细的分页、灵活的多字段排序,以及基于前端动态传入 field
(搜索字段) 和 value
(搜索值) 的动态条件搜索。我们将再次亮出 Spring Boot 和 Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) 的组合拳,特别是利用 JpaSpecificationExecutor
来展现动态查询的魅力,并通过 DTO (Data Transfer Object, 数据传输对象) 模式确保 API (Application Programming Interface, 应用程序编程接口) 响应的专业与安全。准备好打造你自己的币种管理“瑞士军刀”了吗?让我们一同启程!️
特性/方面 | 描述 | 关键技术/模式 |
---|---|---|
核心功能 | 查询币种 (Currency ) 列表。 |
Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口)。 |
分页支持 | 支持标准的分页参数 page (页码) 和 size (每页大小)。 |
自定义 BasePage (或 PageWithSearch ) 类及其 toPageable() 方法,生成 Spring Data Pageable 对象。 |
↕️ 排序支持 | 支持基于一个或多个币种记录字段 (properties ) 的升序 (ASC ) 或降序 (DESC ) (direction ) 排序。 |
BasePage 处理排序参数,Sort.by() 构建 Sort 对象。 |
动态搜索 | 支持前端传递 field (要搜索的字段名,如 “code”, “name”, “symbol”) 和 value (搜索值) 进行条件查询。 |
JpaSpecificationExecutor , 自定义 CurrencySpecification 类动态构建 Predicate 。 |
✨ API (Application Programming Interface, 应用程序编程接口) 响应 | 返回统一的 BaseResult 结构,数据部分为 Page ,使用 DTO (Data Transfer Object, 数据传输对象) 避免序列化问题并控制暴露字段。 |
DTO (Data Transfer Object, 数据传输对象) 转换在 Service 层完成。 |
️ 数据准确性 | (如果适用)对搜索字段值的类型转换进行处理,确保查询的健壮性。 | CurrencySpecification 内部的类型转换和异常处理。 |
技术栈核心 | Java (一种面向对象的编程语言), Spring Boot, Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口), Hibernate (Java 的一个对象关系映射框架), MySQL (一种关系型数据库管理系统) (或其它), Lombok (一个Java库,可以通过简单的注解形式来帮助消除样板式代码), Swagger (API (Application Programming Interface, 应用程序编程接口) 文档) |
PageWithSearch.java
我们继续使用 PageWithSearch
类作为前端请求参数的载体。它继承自 BasePage
(负责处理 page
, size
, direction
, properties
),并自身添加了 field
和 value
用于通用搜索。
// PageWithSearch.java (关键属性)
public class PageWithSearch extends BasePage {
@ApiParam("搜索字段名 (如: code, name, symbol)")
private String field;
@ApiParam("搜索值")
private String value;
// ... Getters and Setters ...
// BasePage.toPageable() 将 page, size, direction, properties 转为 Pageable
}
前端可以通过类似 GET /api/v1/admin/currencies?page=0&size=10&properties=code&direction=ASC&field=name&value=美
的方式发起请求。
CurrencyDto.java
为了 API (Application Programming Interface, 应用程序编程接口) 的清晰、安全和避免潜在的序列化问题(特别是如果 Currency
实体有复杂关联),我们定义 CurrencyDto
。
// CurrencyDto.java
@Data
public class CurrencyDto {
private Integer id;
private String code;
private String name;
private String symbol;
private BigDecimal exchangeRate;
private Byte isDefault;
private Byte status;
private Date createdDate;
private Date lastModifiedDate;
}
CurrencyRepository.java
此接口继承 JpaRepository
(提供基础 CRUD (Create, Read, Update, Delete))和 JpaSpecificationExecutor
(提供动态条件查询能力)。
// CurrencyRepository.java
@Repository
public interface CurrencyRepository extends JpaRepository<Currency, Integer>,
JpaSpecificationExecutor<Currency> { // 关键:继承 JpaSpecificationExecutor
// ... (其他可能存在的自定义查询方法,如 findByCode) ...
}
CurrencySpecification.java
这是实现动态搜索的核心。它根据 PageWithSearch
中的 field
和 value
构建 JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) Criteria API (Application Programming Interface, 应用程序编程接口) 的 Predicate
。
// CurrencySpecification.java
public class CurrencySpecification {
public static Specification<Currency> buildSpecification(PageWithSearch params) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.hasText(params.getField()) && StringUtils.hasText(params.getValue())) {
String field = params.getField().trim();
String value = params.getValue().trim();
try {
if ("code".equalsIgnoreCase(field)) {
predicates.add(criteriaBuilder.like(root.get("code"), "%" + value.toUpperCase() + "%"));
} else if ("name".equalsIgnoreCase(field)) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + value + "%"));
} else if ("symbol".equalsIgnoreCase(field)) {
predicates.add(criteriaBuilder.like(root.get("symbol"), "%" + value + "%"));
} else if ("status".equalsIgnoreCase(field)) {
predicates.add(criteriaBuilder.equal(root.get("status"), Byte.parseByte(value)));
} // ... 其他可搜索字段 ...
} catch (Exception e) { /* 忽略无效搜索或记录日志 */ }
}
// ... (可以添加其他来自 PageWithSearch 的固定搜索条件) ...
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}
Specification
的魔法 ✨: 它允许我们用 Java (一种面向对象的编程语言) 代码安全地、动态地构建 WHERE
子句,JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) 会将其转换为对应的 SQL (Structured Query Language, 结构化查询语言)。
CurrencyService.java
)Service 层负责编排整个查询流程:从 PageWithSearch
获取分页排序信息,构建 Specification
,调用 Repository 执行查询,最后将实体分页结果转换为 DTO (Data Transfer Object, 数据传输对象) 分页结果。
核心处理流程图 (findCurrencies
)
CurrencyService.java
(关键逻辑):
// ...
@Service
public class CurrencyService {
// ... (依赖注入: CurrencyRepository) ...
@Transactional(readOnly = true)
public Page<CurrencyDto> findCurrencies(Integer adminId, PageWithSearch pageWithSearch) {
// adminId 可用于日志记录或未来可能的权限控制
logger.info("Admin {} searching for currencies with params: {}", adminId, pageWithSearch);
Pageable pageable = pageWithSearch.toPageable();
Specification<Currency> spec = CurrencySpecification.buildSpecification(pageWithSearch);
Page<Currency> currencyPage = currencyRepository.findAll(spec, pageable);
List<CurrencyDto> dtos = currencyPage.getContent().stream()
.map(this::convertToDto) // convertToDto 将 Currency 转为 CurrencyDto
.collect(Collectors.toList());
return new PageImpl<>(dtos, pageable, currencyPage.getTotalElements());
}
private CurrencyDto convertToDto(Currency entity) {
// ... (将 Currency 实体映射到 CurrencyDto) ...
}
}
CurrencyController.java
)Controller 负责暴露 /api/v1/admin/currencies
GET 端点。
// CurrencyController.java
@Api(tags = "币种管理")
@RestController
@RequestMapping("/api/v1/admin/currencies")
public class CurrencyController {
// ... (依赖注入 CurrencyService) ...
@ApiOperation("查询币种列表 (分页、排序和搜索)")
@GetMapping
public BaseResult findCurrencies(
@ApiIgnore @SessionAttribute(name = Constants.ADMIN_ID, required = false) Integer adminId,
PageWithSearch pageWithSearch // Spring MVC 自动绑定请求参数
) {
if (adminId == null) { /* ...返回未登录... */ }
try {
Page<CurrencyDto> currencyDtoPage = currencyService.findCurrencies(adminId, pageWithSearch);
return BaseResult.success("查询成功", currencyDtoPage);
} catch (Exception e) { /* ...错误处理... */ }
}
}
Currency
记录本身)Currency
记录的状态通常比较简单,主要是存在、启用或禁用。
方法签名详细说明(简化版,具体类型参考代码):
CurrencyController.findCurrencies(...)
: 返回 BaseResult
(内含 Page
)。CurrencyService.findCurrencies(...)
: 返回 Page
。CurrencyRepository.findAll(Specification spec, Pageable pageable)
: 返回 Page
。CurrencySpecification.buildSpecification(...)
返回 Specification
。通过 Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) 的 JpaSpecificationExecutor
,我们能够以一种非常优雅和强大的方式来实现动态条件查询。结合自定义的 PageWithSearch
参数对象和精心设计的 DTO (Data Transfer Object, 数据传输对象) 响应,findCurrencies
接口不仅功能完备,而且代码结构清晰、易于扩展和维护。
这套方法论可以广泛应用于你项目中其他所有需要复杂列表查询的场景。掌握它,你就能更从容地应对各种刁钻的查询需求,为用户提供更精准、更便捷的数据服务!
希望这篇关于构建动态可搜索查询接口的博客能给你带来实实在在的帮助!如果你在 Specification
的使用或动态查询优化方面有更多心得,欢迎在评论区留言分享,我们一同进步! Happy Dynamic Querying!