
一篇讲透聚合根用 Spring Data Relational 告别混乱的领域模型【免费下载链接】spring-data-relationalSpring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.项目地址: https://gitcode.com/gh_mirrors/sp/spring-data-relationalSpring Data Relational 是 Spring 生态中负责关系型数据访问的框架旗下 Spring Data JDBC 与 Spring Data R2DBC都把领域驱动设计DDD的聚合根当作持久化的最小单位。要用好它得先想明白一个根本问题聚合根到底是什么它又能替你解决什么先看一个翻车现场订单存了一半钱扣了货没发电商订单系统里最典型的事故长这样用户点提交订单后端先插一条order主表再循环插入 N 条order_item明细再更新一条delivery_address地址记录。如果第 3 步抛了异常而事务又没圈住全部操作——恭喜你数据库里多了一张只有头没有身子的订单钱扣了货永远发不出去。事后排查只能翻日志、对流水、写补偿脚本一地鸡毛。问题出在哪订单、明细、地址本来是一个必须同生共死的整体却被你拆成三条互不相关的 SQL 来裸操作。它们缺的不是事务而是一个总负责人。用机长与航班拆解聚合根的本质在领域驱动设计里这种同生共死的整体叫聚合它的总负责人就是聚合根Aggregate Root。打个比方一趟航班是一个聚合——乘客名单、行李、舱位信息都在其中机长就是聚合根。乘客可以换、行李可以加但一切变动都要经机长这个入口你不能绕过机长随便找一位空乘就把航线改了。外部世界塔台、机场也只认航班号这一个全局标识不会直接指挥某个乘客。对应到代码上聚合根有三条铁律入口唯一对聚合内任何对象的操作都必须从聚合根发起全局 ID聚合根有全局唯一标识内部实体只有本地标识内部自治聚合内的数据一致性由聚合根自己负责保证。一句话聚合根把一坨对象升级成了一个有边界、有入口、有身份的整体。聚合根持久化的正确姿势把生命周期交给框架理解了理念再看框架你会发现 Spring Data Relational 几乎处处在替聚合根操心。用注解标出聚合的骨架根实体用Table标注、主键加Id聚合内的子集合用MappedCollection值对象用Embedded内嵌。框架据此知道谁是根、谁跟着根走。这也正是 Spring Data JDBC 对聚合边界的落地方式官方文档 entity-persistence.adoc 里有完整的注解说明。一次 save全家一起入库聚合根的生命周期管理是框架最核心的卖点。你只需要调用JdbcAggregateTemplate.save(order)框架就会顺着聚合边界自动生成对根、子集合、内嵌对象的一整套 insert / update / delete并在同一个事务里执行orderTemplate.save(order); // 一条调用订单、明细、地址全部处理完毕这个动作的背后是框架把聚合翻译成一串有序的数据库动作。DefaultRootAggregateChange会先校验传入对象必须是聚合根类型否则直接抛出AggregateRoot must be of type ...。校验类型、编排顺序、处理版本号这些脏活框架全包了。仓库只属于聚合根Repository 只给聚合根建。你想查订单用OrderRepository.findById(...)想查某订单下的明细必须从订单这个入口进入。小贴士如果你发现自己忍不住给子实体也建了 Repository先停手。这通常不是多写了一个类而是聚合边界画错了的信号。如何科学地划定聚合边界四个常见坑与纠偏坑一把不相关的东西硬塞进一个聚合 ❌订单聚合里塞进用户积分明细两个生命周期完全独立的东西被强行绑定每次保存都拖着一大串冗余数据性能和可维护性双双崩盘。✅ 正确做法聚合要保持小巧。生命周期、事务边界都不同的东西就该是两个聚合通过 ID 互相引用。坑二跨聚合直接持有对象引用 ❌订单里直接写private Customer customer;一加载就连带整张用户表聚合边界名存实亡。✅ 正确做法只保存对方的 ID。用AggregateReferenceCustomer, Long表达我引用另一个聚合但我只认它的身份需要完整数据时再按 ID 去查。坑三忽略版本控制 ❌两个请求同时改同一订单后提交的默默覆盖先提交的数据静默丢失且无迹可寻。✅ 正确做法给聚合根加Version字段开启乐观锁。框架写入前会携带并校验版本号冲突直接抛异常让并发问题暴露在你面前而不是悄悄写坏数据。坑四事务只圈住一半 ❌save 放在事务里手动修改子表的代码却在事务外异常一来照样裂开。✅ 正确做法聚合的读写永远从聚合根这一个入口进子实体的 SQL 交给框架统一生成并纳入同一事务不要自己越权去裸写子表。五步落地路径把 DDD 实践落到真实项目想真正用起来按下面五步走画边界问一句这些数据必须同生共死吗答案是肯定的才放进同一个聚合标骨架根实体加TableId子集合加MappedCollection内嵌值对象加Embedded建仓库只为聚合根创建 Repository其余实体一律从根访问跨聚合只存 ID引用别的聚合时用AggregateReference绝不持有对象加版本并发敏感的聚合根补上Version字段。写完心里没底仓库里自带大量聚合边界集成测试例如JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests可以对照着验证你的边界设计是否成立。回到开头那个订单问题解决了吗现在再看开头的翻车现场订单、明细、地址被建模为一个聚合Order是聚合根。提交订单只需一次save框架把根与所有成员的 SQL 放进同一个事务执行——要么全部落库要么全部回滚。再也不会出现钱扣了、货没发、单子只有头的诡异状态。聚合根不是设计文档里的一页概念它是 Spring Data Relational 一切持久化行为的起点。把边界画对、入口收拢、版本守住你会发现领域模型清晰了代码好维护了那些曾经半夜叫醒你的数据对不上问题也真的不再发生了。【免费下载链接】spring-data-relationalSpring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.项目地址: https://gitcode.com/gh_mirrors/sp/spring-data-relational创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考