架构之美:为小程序打造一个“零读取”的JPA创建接口

架构之美:为小程序打造一个“零读取”的JPA创建接口

在后端开发中,我们经常需要为不同的客户端(如PC管理后台、小程序App)提供功能相似但上下文完全不同的API (Application Programming Interface)。一个常见的错误是将所有逻辑都堆砌在同一个Service类中,导致代码臃肿、职责混乱。

一个更优雅、更具可维护性的架构,是为不同的“端”创建专属的Service层。

今天,我将带你完整地设计并实现一个POST /api/app/demands(小程序创建需求)接口。我们将看到,如何通过创建专属的AppSolutionDemandService,并利用 existsByIdgetReference 这两个JPA (Java Persistence API) “神器”,构建一个几乎“零读取”、性能极致的创建接口。

业务场景:一个简洁的小程序表单提交 📱

我们的需求非常明确:小程序用户填写一个简单的表单(预算、数量等),提交后为自己创建一个新的“福利需求单”(SolutionDemand)。

核心挑战

  1. 架构清晰:如何将小程序端的业务逻辑与PC后台的管理逻辑清晰地分离开来?
  2. 安全:如何确保创建的需求单准确地关联到当前登录的用户,而不是任何其他人?
  3. 性能:如何以最少的数据库交互,最高效地完成创建操作?

解决方案:专属Service + 轻量级校验 + 实体引用

我们通过一套组合拳,完美地解决了所有挑战。

1. 架构决策:创建AppSolutionDemandService

我们做的第一个、也是最重要的决策,就是拒绝在已有的SolutionDemandService(服务于PC端)中增加if-else逻辑。我们为小程序端创建了一个全新的、职责单一的Service:AppSolutionDemandService

为什么这么做?

  • 职责分离SolutionDemandService的上下文是管理员,而AppSolutionDemandService的上下文是当前登录用户。将它们分开,使得每个Service的逻辑都更纯粹。
  • 高内聚,低耦合:小程序端的所有需求相关逻辑,都内聚在AppSolutionDemandService中,与PC端完全解耦。
  • 易于维护:未来修改小程序逻辑,只需关心AppSolutionDemandService,完全不用担心会影响到PC后台的功能。
2. Service层实现:追求极致性能

在专属的AppSolutionDemandService中,我们的每一步都以性能和安全为最高优先级。

// AppSolutionDemandService.java
@Service
@RequiredArgsConstructor
public class AppSolutionDemandService {

    private final EntityManager entityManager;
    private final SolutionDemandRepository demandRepository;
    private final SolutionUserRepository solutionUserRepository;

    @Transactional
    public AppDemandCreateVO createDemandForApp(Integer currentUserId, AppDemandCreatePayload payload) {
        // 1. 轻量级用户存在性校验
        if (!solutionUserRepository.existsById(currentUserId)) {
            throw new NotFoundException("当前登录用户不存在");
        }

        // 2. 构建实体并复制前端数据
        SolutionDemand newDemand = new SolutionDemand();
        BeanUtils.copyProperties(payload, newDemand);

        // 3. 使用 getReference() 高效获取用户实体引用
        SolutionUser userReference = entityManager.getReference(SolutionUser.class, currentUserId);
        newDemand.setSolutionUser(userReference);

        // 4. 后端填充业务控制字段
        newDemand.setStatus(1);
        newDemand.setDemandCode(generateDemandCode());

        // 5. 保存实体
        SolutionDemand savedDemand = demandRepository.save(newDemand);

        // 6. 转换为精确的VO并返回
        return convertToVO(savedDemand);
    }
    // ...
}

技术解读

  • existsById(currentUserId): 我们没有用findById去加载完整的SolutionUser实体。existsById只会生成一条极其高效的SELECT COUNT(*) SQL (Structured Query Language),用于确认用户ID的合法性。这是我们的第一重性能优化
  • entityManager.getReference(...): 在确认用户存在后,我们使用getReference()来获取一个SolutionUser的“代理”对象。这个操作完全不查询数据库,但足以让JPA在生成INSERT语句时正确填充solution_user_id外键。这是我们的第二重性能优化

成果验证:一份“零读取”的SQL日志 ✨

执行这个精心设计的接口,我们得到的SQL日志堪称完美:

【最终日志】

-- 1. 极轻量级的存在性检查
Hibernate: 
select count(*) as col_0_0_ from solution_user solutionus0_ where solutionus0_.id=?

-- 2. 唯一的写入操作
Hibernate: 
insert into solution_demand (...) values (...)

日志解读

  • 无过度查询:我们没有SELECT任何完整的实体对象,彻底避免了过度查询。
  • 最低交互:整个业务逻辑只产生了一次极轻量级的读(用于校验)和一次写。这是在需要校验用户存在性的前提下,能达到的最低数据库交互次数。

结论:好的架构,让高性能成为必然 💡

这次小程序创建接口的实现,为我们揭示了构建高性能API的几个关键原则:

  1. 架构先行,职责分离:为不同端(PC/App)或不同领域创建专属的Service,是保证代码长期可维护性的基石。
  2. 校验用exists,关联用reference:在“写”操作中,这是一个黄金组合。用existsBy...进行轻量级校验,用getReference()进行零开销的关联设置。
  3. 后端控制业务规则:将状态、编码等核心业务字段的生成逻辑保留在后端,可以保证数据的一致性和安全性。

通过遵循这些原则,我们构建的不再是一个简单的save接口,而是一个在架构、安全和性能上都达到“工业级”标准的、真正专业的API。


附录:图表化总结与深度解析 📊✨

接口设计原则总结表 📋
设计原则核心问题解决方案关键技术/代码
架构清晰 🏛️PC/App逻辑混杂,职责不清创建专属ServiceAppSolutionDemandService
数据安全 🛡️信任前端传入的用户ID使用Token解析出的currentUserId@RequestAttribute
高性能 🚀INSERTSELECT,过度查询轻量级校验 + 实体引用existsById, getReference()
业务健壮 💪关键业务字段由前端控制后端填充newDemand.setStatus(1)
接口处理流程图 (Flowchart) 💡
失败
通过
开始:createDemandForApp
Step 1: 轻量级用户校验
solutionUserRepository.existsById()
抛出 NotFoundException
Step 2: 构建Demand实体
并设置前端传入的字段
Step 3: 获取用户实体引用
entityManager.getReference()
Step 4: 后端填充业务字段
(status, demandCode)
Step 5: 保存Demand实体
repository.save()
完成:返回VO
关键交互时序图 (Sequence Diagram) 🔄
ControllerServiceRepository"EM""数据库"createDemandForApp(userId, payload)1. existsById(userId)执行 SELECT COUNT(*)2. 构建 newDemand 实体3. getReference(SolutionUser.class, userId)无数据库交互返回 SolutionUser 代理对象4. newDemand.setSolutionUser(proxy)5. save(newDemand)6. 执行 INSERT INTO solution_demand ...返回 AppDemandCreateVOControllerServiceRepository"EM""数据库"
实体状态图 (State Diagram) 🚦

SolutionDemand实体在创建接口中的状态流转为例。

"new SolutionDemand()"
"repository.save()"
"事务提交"
Transient
Persistent
Detached
核心类图 (Class Diagram) 🏗️

展示了AppSolutionDemandService与各组件的依赖关系。

"依赖"
"依赖"
"依赖"
"依赖"
AppDemandController
-AppSolutionDemandService appDemandService
AppSolutionDemandService
-EntityManager entityManager
-SolutionDemandRepository demandRepository
-SolutionUserRepository userRepository
+createDemandForApp()
EntityManager
+getReference()
SolutionUserRepository
+existsById()
SolutionDemandRepository
+save()
实体关系图 (Entity Relationship Diagram) 🔗

用ER图的形式更直观地展示SolutionDemandSolutionUser的关系。

SOLUTION_USERintidPKSOLUTION_DEMANDintidPKintsolution_user_idFK创建
思维导图 (Markdown Format) 🧠

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值