别再死记硬背了!用Spring Boot实战案例,5分钟搞懂UML类图的6种关系
用Spring Boot实战拆解UML类图订单系统里的6种关系可视化每次打开UML类图文档看到那些虚线实线箭头菱形就头疼作为Java开发者我们更习惯用代码思考问题。今天我们就用Spring Boot构建一个精简版电商订单系统把抽象的UML关系变成你每天在写的Service、Autowired和extends。1. 环境准备初始化Spring Boot项目我们先快速搭建基础环境。使用Spring Initializr创建项目时勾选这几个关键依赖spring init --dependenciesweb,data-jpa,lombok uml-demo项目结构里重点关注这几个包domain/放实体类对应UML类图的类repository/JPA接口体现实现关系service/业务逻辑演示依赖注入controller/API入口展示关联调用关键配置在application.properties中开启JPA的DDL自动更新方便我们观察实体关系spring.jpa.hibernate.ddl-autoupdate spring.jpa.show-sqltrue2. 依赖关系最临时的合作依赖Dependency是UML中最弱的关系表现为临时性的方法参数或返回值。在我们的订单系统中典型的场景是支付服务调用通知服务Service public class PaymentService { // 方法参数体现依赖关系 public void processPayment(Order order, NotificationService notifier) { if (order.isPaid()) { notifier.sendSms(order.getUser(), 支付成功); } } }UML中表示为虚线箭头对应代码特征不持有对方引用没有成员变量仅在方法内部临时使用Spring中常见于工具类的静态方法调用提示过度使用依赖关系会导致代码耦合建议通过事件机制如Spring Event解耦3. 关联关系稳定的对象链接关联Association是对象间持久的引用关系在Spring中通常表现为3.1 单向关联用户拥有地址Entity Data public class User { Id GeneratedValue private Long id; // 单向关联用户知道地址地址不知道用户 OneToOne private Address defaultAddress; } Entity Data public class Address { private String province; private String city; }数据库表结构会生成外键约束ALTER TABLE user ADD CONSTRAINT fk_address FOREIGN KEY (default_address_id) REFERENCES address(id)3.2 双向关联订单与商品的多对多Entity Data public class Order { ManyToMany JoinTable(name order_product, joinColumns JoinColumn(name order_id), inverseJoinColumns JoinColumn(name product_id)) private SetProduct products new HashSet(); } Entity Data public class Product { ManyToMany(mappedBy products) private SetOrder orders new HashSet(); }UML中用实线表示代码特征有明确的成员变量引用可能是单向或双向的JPA中用OneToMany/ManyToOne等注解配置4. 聚合与组合整体与部分的哲学4.1 聚合可独立存在的购物车体系聚合Aggregation用空心菱形表示特点是部分可以脱离整体存在Entity Data public class ShoppingCart { OneToMany(cascade CascadeType.PERSIST) private ListCartItem items new ArrayList(); } Entity Data public class CartItem { private Integer quantity; ManyToOne private Product product; }当删除购物车时购物车项仍然可以保留比如转移到其他购物车。数据库表现为-- 删除购物车不会级联删除cart_item DELETE FROM shopping_cart WHERE id 1; -- cart_item表记录仍然存在4.2 组合生命周期绑定的订单明细组合Composition用实心菱形表示特点是部分必须随整体消亡Entity Data public class Order { OneToMany(cascade CascadeType.ALL, orphanRemoval true) JoinColumn(name order_id) private ListOrderItem items new ArrayList(); } Entity Data public class OrderItem { private Integer quantity; ManyToOne private Product product; }关键区别在于cascade CascadeType.ALL和orphanRemoval配置-- 删除订单会级联删除所有order_item DELETE FROM orders WHERE id 1; -- 自动执行 DELETE FROM order_item WHERE order_id 15. 泛化与实现继承体系的表达5.1 泛化关系支付方式的继承树泛化Generalization就是Java中的继承关系UML用空心三角箭头表示Entity Inheritance(strategy InheritanceType.SINGLE_TABLE) DiscriminatorColumn(name payment_type) public abstract class Payment { Id GeneratedValue private Long id; private BigDecimal amount; } Entity DiscriminatorValue(ALIPAY) public class AlipayPayment extends Payment { private String alipayAccount; } Entity DiscriminatorValue(WECHAT) public class WechatPayment extends Payment { private String openid; }数据库采用单表继承策略CREATE TABLE payment ( id BIGINT PRIMARY KEY, amount DECIMAL(19,2), payment_type VARCHAR(20), alipay_account VARCHAR(255), openid VARCHAR(255) );5.2 实现关系服务层接口契约实现Realization对应Java的接口实现UML用虚线三角箭头表示public interface PaymentGateway { PaymentResult process(PaymentRequest request); } Service public class AlipayGateway implements PaymentGateway { Override public PaymentResult process(PaymentRequest request) { // 具体支付宝实现 } }Spring中常见用法定义接口约束行为通过Autowired注入具体实现支持多态和策略模式6. 综合应用订单系统的完整类图现在我们把所有关系整合到一个电商系统中// 组合关系 Entity public class Order { OneToMany(cascade ALL, orphanRemoval true) private ListOrderItem items; // 关联关系 ManyToOne private User user; // 依赖关系方法参数 public void applyCoupon(Coupon coupon) { //... } } // 泛化关系 public abstract class Notification {} public class EmailNotification extends Notification {} // 实现关系 public interface SearchService {} Service public class ProductSearchService implements SearchService {} // 聚合关系 Entity public class Warehouse { OneToMany private ListProduct products; }对应的UML类图要点订单与订单项是组合关系实心菱形仓库与商品是聚合关系空心菱形通知服务的继承是泛化关系空心三角实线搜索服务的接口是实现关系空心三角虚线订单使用优惠券是依赖关系虚线箭头7. 调试技巧如何验证你的UML实现在开发过程中可以用这些方法验证关系是否正确数据库表结构检查-- 查看外键约束 SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE FOREIGN KEY; -- 查看继承策略的表字段 DESCRIBE payment;JPA日志分析# 开启Hibernate日志 logging.level.org.hibernate.SQLDEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinderTRACE单元测试验证生命周期Test public void testComposition() { Order order new Order(); order.getItems().add(new OrderItem()); orderRepository.save(order); // 验证删除订单是否级联删除明细 orderRepository.delete(order); assertThat(orderItemRepository.count()).isZero(); }Spring上下文检查// 验证依赖注入是否符合预期 assertThat(applicationContext.getBean(PaymentGateway.class)) .isInstanceOf(AlipayGateway.class);8. 避坑指南常见建模错误在实际项目中我见过这些典型的UML误用混淆聚合与组合错误把购物车和商品设计成组合关系正确商品可以独立存在应该是聚合过度使用继承// 反模式用继承实现支付方式折扣 public class Payment { public BigDecimal getDiscount() { /*...*/ } } public class AlipayPayment extends Payment { Override public BigDecimal getDiscount() { /*...*/ } }更好的做法是用策略模式public interface DiscountStrategy { BigDecimal apply(BigDecimal amount); }循环依赖陷阱Service public class AService { Autowired BService b; } Service public class BService { Autowired AService a; }解决方案使用Lazy延迟注入提取公共逻辑到第三方服务改用事件驱动架构JPA注解误配// 错误组合关系缺少级联删除 OneToMany private ListOrderItem items; // 正确配置 OneToMany(cascade ALL, orphanRemoval true) private ListOrderItem items;9. 扩展思考DDD中的建模实践当项目采用领域驱动设计时UML类图会呈现新的特点聚合根Aggregate Root// Order是聚合根负责维护内部一致性 public class Order { private ListOrderItem items; public void addItem(Product p, int qty) { if (qty 0) throw new IllegalArgumentException(); items.add(new OrderItem(p, qty)); } }值对象Value ObjectEmbeddable public class Address { private String city; private String street; // 没有唯一标识 // 不可变setter私有 }领域服务Domain Servicepublic interface PricingService { Money calculatePrice(Order order); }工厂模式Factorypublic interface PaymentFactory { Payment create(PaymentMethod method); }这些模式在UML中的表现聚合根用组合关系管理内部实体值对象用关联关系表示领域服务用依赖关系体现工厂用实现关系绑定具体创建逻辑