币种管理的“瑞士军刀”:构建动态可搜索的币种查询 Spring Data JPA ✨

币种管理的“瑞士军刀”:构建动态可搜索的币种查询 API (Application Programming Interface, 应用程序编程接口) (Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) 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, 应用程序编程接口) 文档)

️ 构建币种查询接口的蓝图

1. 前端请求的“导航图”:PageWithSearch.java

我们继续使用 PageWithSearch 类作为前端请求参数的载体。它继承自 BasePage(负责处理 page, size, direction, properties),并自身添加了 fieldvalue 用于通用搜索。

// 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=美 的方式发起请求。

2. API (Application Programming Interface, 应用程序编程接口) 响应的“标准照”: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;
}

3. 数据访问的“主力军”:CurrencyRepository.java

此接口继承 JpaRepository(提供基础 CRUD (Create, Read, Update, Delete))和 JpaSpecificationExecutor(提供动态条件查询能力)。

// CurrencyRepository.java
@Repository
public interface CurrencyRepository extends JpaRepository<Currency, Integer>,
        JpaSpecificationExecutor<Currency> { // 关键:继承 JpaSpecificationExecutor
    // ... (其他可能存在的自定义查询方法,如 findByCode) ...
}

4. 动态查询条件的“建筑师”:CurrencySpecification.java

这是实现动态搜索的核心。它根据 PageWithSearch 中的 fieldvalue 构建 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, 结构化查询语言)。

5. Service 层:业务逻辑的“总指挥” (CurrencyService.java)

Service 层负责编排整个查询流程:从 PageWithSearch 获取分页排序信息,构建 Specification,调用 Repository 执行查询,最后将实体分页结果转换为 DTO (Data Transfer Object, 数据传输对象) 分页结果。

核心处理流程图 (findCurrencies)

开始
Service 接收 adminId, PageWithSearch
(可选) adminId 权限校验
调用 pageWithSearch.toPageable()
获取 Pageable 对象 (含排序)
调用 CurrencySpecification.buildSpecification
(pageWithSearch)
得到 Specification spec
(含动态搜索条件)
(Repo) 调用 currencyRepository.findAll(spec, pageable)
获取 Page 实体分页结果
遍历实体列表, 调用 convertToDto()
将 Page 转为 Page
返回 Page 给 Controller
结束

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

6. Controller 层:API (Application Programming Interface, 应用程序编程接口) 的“接待员” (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) { /* ...错误处理... */ }
    }
}

交互时序图 (Sequence Diagram - 查询币种)

"前端" "Controller" "Service" "CurrencySpecification" "CurrencyRepository" "数据库" GET /currencies (PageWithSearch params) findCurrencies(adminId, pageWithSearch) pageWithSearch.toPageable() -> pageable buildSpecification(pageWithSearch) Specification spec findAll(spec, pageable) 执行动态构建的SQL (SELECT ... WHERE (field LIKE/EQUAL value) ... ORDER BY ... LIMIT ... OFFSET ...) 返回 Page 实体 currencyPage 遍历 currencyPage.getContent(), 调用 convertToDto() Page 构造 BaseResult JSON(BaseResult with Page) "前端" "Controller" "Service" "CurrencySpecification" "CurrencyRepository" "数据库"

状态图 (State Diagram - Currency 记录本身)

Currency 记录的状态通常比较简单,主要是存在、启用或禁用。

"币种未创建"
"ensureCurrency() 或 createCurrency()
status=1"
"updateCurrency() status=0"
"updateCurrency() status=1"
"deleteCurrency() (如果实现)"
"deleteCurrency() (如果实现)"
NonExistent
"已启用"
"已禁用"

️ 类图 (Class Diagram - 关键组件)

"consumes"
"creates & returns"
PageWithSearch
+String field
+String value
+Pageable toPageable()
CurrencyDto
%% DTO for Response %%
CurrencyController
+BaseResult findCurrencies(adminId, PageWithSearch)
CurrencyService
+Page~CurrencyDto~ findCurrencies(adminId, PageWithSearch)
-CurrencyDto convertToDto(Currency)
Currency
%% Entity %%
+String code
+String name
+String symbol
+Byte status
«interface»
CurrencyRepository
%% JpaSpecificationExecutor methods
+Page~Currency~ findAll(Specification, Pageable)
«static utility»
CurrencySpecification
+Specification~Currency~ buildSpecification(PageWithSearch)

方法签名详细说明(简化版,具体类型参考代码):

  • CurrencyController.findCurrencies(...): 返回 BaseResult (内含 Page)。
  • CurrencyService.findCurrencies(...): 返回 Page
  • CurrencyRepository.findAll(Specification spec, Pageable pageable): 返回 Page
  • CurrencySpecification.buildSpecification(...) 返回 Specification

英文缩写全称及中文解释

  • API: Application Programming Interface (应用程序编程接口)
  • ASC: Ascending (升序)
  • CRUD: Create, Read, Update, Delete (增删改查)
  • DESC: Descending (降序)
  • DTO: Data Transfer Object (数据传输对象)
  • HTTP: HyperText Transfer Protocol (超文本传输协议)
  • ID: Identifier (标识符)
  • JPA: Jakarta Persistence API (formerly Java Persistence API) (Jakarta 持久化应用程序接口)
  • JSON: JavaScript Object Notation (JavaScript 对象表示法)
  • MVC: Model-View-Controller (模型-视图-控制器)
  • ORM: Object-Relational Mapping (对象关系映射)
  • POJO: Plain Old Java Object (简单Java对象)
  • SQL: Structured Query Language (结构化查询语言)
  • UML: Unified Modeling Language (统一建模语言)

思维导图 (Markdown 格式)

总结:为数据查询插上动态的翅膀!

通过 Spring Data JPA (Jakarta Persistence API, Jakarta 持久化应用程序接口) 的 JpaSpecificationExecutor,我们能够以一种非常优雅和强大的方式来实现动态条件查询。结合自定义的 PageWithSearch 参数对象和精心设计的 DTO (Data Transfer Object, 数据传输对象) 响应,findCurrencies 接口不仅功能完备,而且代码结构清晰、易于扩展和维护。

这套方法论可以广泛应用于你项目中其他所有需要复杂列表查询的场景。掌握它,你就能更从容地应对各种刁钻的查询需求,为用户提供更精准、更便捷的数据服务!

希望这篇关于构建动态可搜索查询接口的博客能给你带来实实在在的帮助!如果你在 Specification 的使用或动态查询优化方面有更多心得,欢迎在评论区留言分享,我们一同进步! Happy Dynamic Querying!

你可能感兴趣的:(Spring,Data,JPA,jpa)