DDD学习第4课仓储与持久化隔离(使用数据库)

发布时间:2026/9/14 15:14:25

DDD学习第4课仓储与持久化隔离(使用数据库) 第4课仓储模式与持久化隔离本节目标理解仓储Repository的作用使用Spring Data JPA将Order聚合保存到数据库保持领域层“纯净”不依赖框架展示Order聚合的数据库持久化与查询仓储Repository的职责在 DDD 中仓储是领域层与持久化层的分界线。角色职责领域层只关心业务对象Order、Menu 等仓储接口定义持久化操作save、findById基础设施层实现通过 JPA / MyBatis / Redis 实现实际的数据访问重点仓储接口放在domain.repository包里属于领域层。实现类放在infrastructure.repository包里属于基础设施层。修改pom.xml添加 Spring Data JPA 和 H2 数据库依赖内存数据库方便演示dependencies!-- Web 基础依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- 数据持久化Spring Data JPA --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- 内存数据库 H2 --dependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdscoperuntime/scope/dependency/dependencies持久化实体设计我们不能直接把领域模型Order当成 JPA 实体那会污染领域层。所以我们新增一个持久化对象Entity来对应数据库结构。OrderEntity.javapackagecom.example.ordersystem.infrastructure.persistence.entity;importjakarta.persistence.*;importjava.util.ArrayList;importjava.util.List;/** * JPA 实体对象仅用于持久化 * 与领域模型 Order 分离 */EntityTable(nameorders)publicclassOrderEntity{IdprivateStringid;privateStringstatus;privatedoubletotalAmount;// 一对多关联到 OrderItemEntityOneToMany(mappedByorder,cascadeCascadeType.ALL,orphanRemovaltrue)privateListOrderItemEntityitemsnewArrayList();publicOrderEntity(){}publicOrderEntity(Stringid,Stringstatus,doubletotalAmount){this.idid;this.statusstatus;this.totalAmounttotalAmount;}publicStringgetId(){returnid;}publicStringgetStatus(){returnstatus;}publicdoublegetTotalAmount(){returntotalAmount;}publicListOrderItemEntitygetItems(){returnitems;}publicvoidaddItem(OrderItemEntityitem){item.setOrder(this);items.add(item);}}OrderItemEntity.javapackagecom.example.ordersystem.infrastructure.persistence.entity;importjakarta.persistence.*;/** * JPA 实体对象订单项 */EntityTable(nameorder_items)publicclassOrderItemEntity{IdGeneratedValue(strategyGenerationType.IDENTITY)privateLongid;privateStringname;privateintquantity;privatedoubleprice;ManyToOne(fetchFetchType.LAZY)JoinColumn(nameorder_id)privateOrderEntityorder;publicOrderItemEntity(){}publicOrderItemEntity(Stringname,intquantity,doubleprice){this.namename;this.quantityquantity;this.priceprice;}publicvoidsetOrder(OrderEntityorder){this.orderorder;}// Getter 省略可补充}JPA Repository 接口在基础设施层定义 Spring Data 的仓储接口。packagecom.example.ordersystem.infrastructure.persistence.jpa;importcom.example.ordersystem.infrastructure.persistence.entity.OrderEntity;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.stereotype.Repository;/** * JPA仓储接口 * 直接继承Spring Data JpaRepository */RepositorypublicinterfaceJpaOrderRepositoryextendsJpaRepositoryOrderEntity,String{}仓储实现类DDD Repository 实现我们要把领域模型Order ↔ 持久化实体OrderEntity互相转换。packagecom.example.ordersystem.infrastructure.repository;importcom.example.ordersystem.domain.order.model.*;importcom.example.ordersystem.domain.order.repository.OrderRepository;importcom.example.ordersystem.infrastructure.persistence.entity.OrderEntity;importcom.example.ordersystem.infrastructure.persistence.entity.OrderItemEntity;importcom.example.ordersystem.infrastructure.persistence.jpa.JpaOrderRepository;importorg.springframework.stereotype.Repository;RepositorypublicclassJpaOrderRepositoryImplimplementsOrderRepository{ResourceLazyJpaOrderRepositoryjpaRepository;Overridepublicvoidsave(Orderorder){// 将领域模型转换为 JPA 实体OrderEntityentitynewOrderEntity(order.getId().getValue(),order.getStatus().name(),order.getTotalAmount().getAmount());// 添加 OrderItemorder.getItems().forEach(item-{OrderItemEntityitemEntitynewOrderItemEntity(item.getName(),item.getQuantity(),item.getPrice().getAmount());entity.addItem(itemEntity);});jpaRepository.save(entity);}OverridepublicOrderfindById(OrderIdid){OrderEntityentityjpaRepository.findById(id.getValue()).orElseThrow(()-newRuntimeException(订单不存在: id.getValue()));// 将 JPA 实体转换回领域模型OrderordernewOrder(newOrderId(entity.getId()));entity.getItems().forEach(item-order.addMenuItem(// 使用 MenuItem 模拟此时价格已持久化newcom.example.ordersystem.domain.menu.model.MenuItem(item.getName(),item.getPrice()),item.getQuantity()));// 恢复状态switch(entity.getStatus()){casePAID-order.pay();caseCONFIRMED-order.confirm();caseCOMPLETED-order.complete();caseCANCELLED-order.cancel();}returnorder;}}修改OrderLifecycleServiceimportcom.example.ordersystem.domain.menu.model.Menu;importcom.example.ordersystem.domain.menu.model.MenuItem;importcom.example.ordersystem.domain.order.model.Order;importcom.example.ordersystem.domain.order.model.OrderId;importcom.example.ordersystem.domain.order.repository.OrderRepository;importorg.springframework.stereotype.Service;/** * 应用服务管理订单生命周期 */ServicepublicclassOrderLifecycleService{privatefinalOrderRepositoryrepository;privatefinalMenumenu;publicOrderLifecycleService(OrderRepositoryrepository,Menumenu){this.repositoryrepository;this.menumenu;}/** 创建订单 */publicOrdercreateOrder(){OrderordernewOrder(OrderId.newId());MenuItemfriedRicemenu.findItem(Fried Rice);MenuItemcokemenu.findItem(Coke);order.addMenuItem(friedRice,1);order.addMenuItem(coke,2);repository.save(order);returnorder;}publicOrderpayOrder(StringorderId){Orderorderrepository.findById(newOrderId(orderId));order.pay();repository.save(order);returnorder;}publicOrderconfirmOrder(StringorderId){Orderorderrepository.findById(newOrderId(orderId));order.confirm();repository.save(order);returnorder;}}修改OrderSystemApplicationimportorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;importorg.springframework.scheduling.annotation.EnableAsync;importorg.springframework.web.client.RestTemplate;importcom.example.ordersystem.domain.menu.model.Menu;importcom.example.ordersystem.domain.menu.model.MenuId;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;EnableAsyncSpringBootApplication(scanBasePackages{com.example})publicclassOrderSystemApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderSystemApplication.class,args);}/** * 初始化菜单 */BeanpublicMenuinitMenu(){MenumenunewMenu(MenuId.newId());menu.addItem(Fried Rice,12.5);menu.addItem(Coke,3.0);returnmenu;}}删除InMemoryOrderRepository配置数据库在application.yml中添加spring:datasource:url:jdbc:h2:file:d:/data/testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSEdriver-class-name:org.h2.Driverusername:sapassword:jpa:hibernate:ddl-auto:updateshow-sql:true测试运行启动应用后访问POST http://localhost:8080/orders/create观察控制台输出的 SQLinsertintoorders...insertintoorder_items...表示订单已经被成功持久化到数据库。你可以再访问POST http://localhost:8080/orders/{id}/pay会发现状态被正确更新数据库中的记录也会变化。第4课总结要点说明仓储接口domain定义存取方法不依赖技术框架仓储实现infrastructure实现持久化细节使用 JPAJPA 实体映射数据库表不影响领域模型领域层依旧纯净不直接使用 JPA 注解或依赖 Spring
延伸阅读

更多相关文章

2026/9/14 15:21:44

Linux shadow 文件详解:恢复 root 密码的完整指南

一. shadow 文件简介在 Linux 系统中,/etc/shadow 文件用于存储用户密码的加密信息以及密码相关的策略属性。它与 /etc/passwd 文件配合使用,passwd 文件保存用户基本信息(且对所有用户可读),而 shadow 文件中存放的加…

2026/9/12 12:23:19

PAGravf重力场逼近的误差分析与精度评定原理及方法

来源:地球物理大地测量科学计算平台,章传银地球重力场无缝连续地充满于整个地固空间,我们只能用一定分辨率的格网空间平均值来表达重力场量,这是重力场量区别于一般离散点几何大地测量要素的典型特征,重力场逼近的误差…

2026/9/14 18:50:19

React Native集成YOLO11实现移动端实时物体检测

1. React Native与YOLO11结合的背景与价值移动端实时物体检测正在成为计算机视觉领域的重要应用方向。传统方案通常需要将图像数据上传至云端服务器进行处理,这种方式存在延迟高、隐私风险大、网络依赖性强等明显缺陷。而React Native作为跨平台移动开发框架&#x…

2026/9/14 18:45:19

Node.js+Vue+ECharts:学生课外活动管理系统可视化大屏实战

先用一句话讲清楚这个项目是干什么的:这是一套以 Node.js 做后端、Vue 做前端的学生课外活动管理系统,在完成报名、审核、积分等常规业务的同时,单独抽出一块“数据可视化大屏分析系统”,用图表方式把活动分布、参与热度、学院排名…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码