You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:00:15