SpringBoot中如何在不创建直接依赖的情况下跨模块使用实体?
针对你在DDD模块拆分下的实体解耦需求,这里提供几种实用方案,完全贴合Spring生态和JPA的使用场景:
方案1:基于标识的弱关联(最贴合Django get_user_model 思路)
核心是不在实体层直接关联跨模块实体,只存储用户的唯一标识(比如username),在业务层通过Core模块定义的抽象接口获取用户信息,彻底隔离模块依赖。
修改NotificationEntity:
@Entity @Table(name = "notification") public class NotificationEntity { @Id @GeneratedValue @Setter @Getter private Long id; // 仅存储用户标识,不直接关联UserEntity @Column(name = "username") @Setter @Getter private String username; }
在Core模块定义用户服务的抽象接口和值对象:
// Core模块的抽象接口,定义跨模块需要的用户操作 public interface UserService { // 只返回Notification模块需要的用户字段 UserInfo getUserInfoByUsername(String username); } // Core模块的用户信息值对象,仅包含共享字段 public record UserInfo(String username, String roles, boolean isActive) {}
User模块实现该接口:
@Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserInfo getUserInfoByUsername(String username) { UserEntity user = userRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("用户不存在")); return new UserInfo(user.getUsername(), user.getRoles(), user.isActive()); } }
Notification模块通过依赖Core的抽象接口获取用户信息:
@Service public class NotificationService { private final NotificationRepository notificationRepository; private final UserService userService; // 仅依赖Core抽象,不依赖User模块 public NotificationService(NotificationRepository notificationRepository, UserService userService) { this.notificationRepository = notificationRepository; this.userService = userService; } public NotificationWithUser getNotificationWithUser(Long notificationId) { NotificationEntity notification = notificationRepository.findById(notificationId) .orElseThrow(() -> new RuntimeException("通知不存在")); UserInfo userInfo = userService.getUserInfoByUsername(notification.getUsername()); return new NotificationWithUser(notification.getId(), userInfo); } } // Notification模块的DTO,用于组装返回结果 public record NotificationWithUser(Long id, UserInfo userInfo) {}
这个方案完全遵循DDD模块隔离原则,Notification模块仅依赖Core,和User模块无直接耦合,和Django依赖抽象的思路完全一致。
方案2:基于Core抽象实体的关联(适合需要JPA关联特性的场景)
如果业务需要JPA的级联查询、数据库约束等特性,可以在Core模块定义抽象用户实体,User模块的具体实体继承它,Notification模块关联这个抽象实体,避免直接依赖User模块。
Core模块定义抽象实体:
// Core模块的抽象用户实体,仅保留跨模块共享字段 @MappedSuperclass public abstract class AbstractUserEntity implements Serializable { @Id private Long id; @Column(unique = true) private String username; // 仅定义跨模块需要的getter public String getUsername() { return username; } public Long getId() { return id; } }
User模块的具体实体继承抽象类:
@Entity public class UserEntity extends AbstractUserEntity { // 仅保留User模块独有的字段 private String password; private String roles; private boolean isActive; private boolean isStaff; private boolean isSuperUser; // 自定义getter/setter }
Notification模块关联Core的抽象实体:
@Entity @Table(name = "notification") public class NotificationEntity { @Id @GeneratedValue @Setter @Getter private Long id; // 关联Core的抽象实体,而非User模块的具体实体 @ManyToOne @JoinColumn(name = "username", referencedColumnName = "username") @Setter @Getter private AbstractUserEntity user; }
Hibernate完全支持关联映射到@MappedSuperclass抽象类,此时Notification模块仅依赖Core的抽象实体,不直接耦合User模块,同时保留JPA的关联特性。
方案3:值对象+JPA转换器(适合轻量级快照场景)
如果仅需要存储用户的固定快照信息(不需要实时同步),可以在Core模块定义UserInfo值对象,通过JPA的@Convert将其映射为数据库字段,彻底避免实体关联。
Core模块定义值对象和转换器:
// Core模块的UserInfo值对象 public record UserInfo(String username, String roles) {} // Core模块的JPA转换器,自动处理值对象和数据库字符串的转换 @Converter(autoApply = true) public class UserInfoConverter implements AttributeConverter<UserInfo, String> { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(UserInfo userInfo) { try { return objectMapper.writeValueAsString(userInfo); } catch (JsonProcessingException e) { throw new RuntimeException("转换UserInfo失败", e); } } @Override public UserInfo convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, UserInfo.class); } catch (JsonProcessingException e) { throw new RuntimeException("解析UserInfo失败", e); } } }
Notification模块直接使用值对象:
@Entity @Table(name = "notification") public class NotificationEntity { @Id @GeneratedValue @Setter @Getter private Long id; // 直接存储UserInfo值对象,JPA自动转换为JSON字符串 @Column(name = "user_info") @Setter @Getter private UserInfo userInfo; }
创建通知时,通过Core的UserService获取UserInfo并设置到实体中即可,适合不需要实时同步用户信息的场景。
选型建议
- 优先选方案1:完全解耦,符合DDD设计原则,和Django的思路最匹配。
- 需要JPA关联特性选方案2:通过抽象实体实现依赖倒置,保留JPA能力。
- 轻量级快照场景选方案3:实现简单,无需额外服务层逻辑。
内容的提问来源于stack exchange,提问作者Sajad Rezvani

