DDD学习第4课仓储与持久化隔离(使用数据库) 📅 2026/7/15 8:14:14 第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