尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
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
RELATED

相关推荐

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

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

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

📅 2026/9/14 18:00:11
QQ音乐加密音频解密终极指南:qmcdump让你的音乐文件重获自由

QQ音乐加密音频解密终极指南:qmcdump让你的音乐文件重获自由

QQ音乐加密音频解密终极指南:qmcdump让你的音乐文件重获自由 【免费下载链接】qmcdump 一个简单的QQ音乐解码(qmcflac/qmc0/qmc3 转 flac/mp3),仅为个人学习参考用。 项目地址: https://gitcode.com/gh_mirrors/qm/qmcdump …

📅 2026/9/12 1:35:09
PIC18F46K40与M95M02-DR EEPROM的嵌入式数据存储方案

PIC18F46K40与M95M02-DR EEPROM的嵌入式数据存储方案

1. 项目背景与核心需求在嵌入式系统开发中,数据存储的可靠性往往决定着整个系统的稳定性。M95M02-DR这颗2Mbit的EEPROM芯片与PIC18F46K40微控制器的组合,恰好能满足工业级应用对非易失性存储的严苛要求。我最近在一个环境监测设备项目中采用了这套方案&a…

📅 2026/9/8 19:20:00
MORE NEWS

更多资讯

📰

OpenClaw实战:AI Agent手写爬虫抓取GitHub releases的踩坑与方案

2026 年 3 月 28 日,我在做 OpenClaw 的一次例行升级时,被一个看似简单的需求卡了整整半天:让 AI 自己去 GitHub 拿一份 release 清单和 skill 仓库说明。折腾到最后,连 OpenClaw 里的 AI Agent 都被逼无奈,放弃了内置…

📰

Matlab读取Excel数据与分类标签处理实战

1. 项目概述:Excel数据读取与分类标签处理刚接手一个数据分析项目时,最基础也最关键的一步就是数据读取。很多新手会卡在这个看似简单的环节,特别是当数据格式和结构有特定要求时。今天我们就来彻底解决这个问题——如何在Matlab中正确读取Ex…

📰

SpringBoot3整合Mybatis实战:简化Java数据访问层开发

1. SpringBoot3与Mybatis整合概述 SpringBoot3作为当前Java生态中最主流的应用开发框架,其简化配置和快速启动的特性深受开发者喜爱。而Mybatis作为持久层框架的经典选择,凭借灵活的SQL编写方式和优秀的性能表现,在企业级应用中占据重要地位。…

📰

SadTalker 安装部署指南:macOS / Windows / WSL / Docker 多平台环境搭建实战

SadTalker 安装部署指南:macOS / Windows / WSL / Docker 多平台环境搭建实战 【免费下载链接】SadTalker [CVPR 2023] SadTalker:Learning Realistic 3D Motion Coefficients for Stylized Audio-Driven Single Image Talking Face Animation 项目地址…

📰

Python开发Discord机器人:从入门到实践

1. 项目概述 Discord作为全球最流行的即时通讯平台之一,其机器人生态已经发展成为一个庞大的开发者社区。根据Discord官方数据,目前平台上有超过300万个活跃的机器人,每天处理数十亿条消息。使用Python开发Discord机器人之所以成为主流选择&…

📰

Hindsight Supabase 租户扩展深度解析:本地 JWKS 验证、按用户 Schema 隔离与内置版本迁移

Hindsight Supabase 租户扩展深度解析:本地 JWKS 验证、按用户 Schema 隔离与内置版本迁移 【免费下载链接】hindsight Hindsight: Agent Memory That Learns 项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight 本篇指南聚焦 Hindsight 仓…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬