Java LLD 中实体关联应使用对象引用还是 ID?数据应存在哪里?
结论
Payment 可以保存 User 对象的引用,这对同一进程内的对象模型来说是合理的。是否改用用户 ID,取决于 Payment 是否需要独立加载、存储或跨接口传输。
当前需要调整的是字段命名:private User userID; 的类型是 User,保存的是对象引用,字段应叫 user。如果字段叫 userID,类型就应与 User.userID 一致,即 Long。
User、Payment 和 Flight 的集中存储与查询可以交给 Repository 或类似的数据访问组件。实体字段负责表达业务关系,不负责管理系统中的全部记录。
保存 User 引用意味着什么?
可以把 Payment 中的关联字段改成:
private User user;
把已有的 User 赋给这个字段时,Java 保存的是引用,不会自动复制整个 User。通过 Payment 可以访问关联的 User。如果多处引用同一个可变的 User 对象,一处的修改也可能被其他引用观察到。
这种方式适合在同一进程内直接访问用户信息。它本身不会把数据保存到数据库,也不会规定对象如何序列化。
如果使用 ORM,还需要配置对象关联如何映射到数据库。Java 字段的类型是 User,并不意味着数据库必须保存整个 User 对象。
什么时候更适合只保存 ID?
如果 Payment 主要需要记录这笔支付属于哪个用户,而用户信息由其他组件查询,保存 ID 通常更直接:
import java.util.UUID;
public class Payment {
private UUID paymentID;
private Long userID;
private PaymentType paymentType;
private Boolean isRefund;
private double amount;
}
这里保留了原有字段,只调整了用户关联的类型。需要用户详情时,再通过 userID 查询 User。
可以参考以下场景选择:
| 场景 | 可采用的设计 |
|---|---|
| 同一进程内,经常需要访问关联用户 | 保存 User 引用 |
| Payment 需要独立加载或存储 | 保存用户 ID 是一种直接的方案 |
| 请求、响应或消息只需要标识用户 | 传递用户 ID,或使用专门的 DTO |
保存 ID 不会自动保证用户存在。创建 Payment 时应验证关联用户。如果使用关系数据库,也可以通过外键约束保证引用完整性。
先确定 User 与 Payment 的关系
原代码同时包含 User.payment 和 Payment.userID,相当于准备从两个方向维护关联。这需要保证两边始终一致,还可能导致 JSON 序列化时出现循环引用。
题目没有说明一个 User 可以有多少笔 Payment,因此还不能确定关系类型:
- 如果一个用户只对应一笔 Payment,单个
payment字段可能符合需求。 - 如果一个用户可以有多笔 Payment,单个字段就不足以表达关系。
- 如果只需要按用户查询支付记录,可以让 Payment 关联用户,再提供按
userID查询的方法,不必在 User 中维护全部 Payment。
先确认业务上的数量关系,再决定是否需要双向关联。
User、Payment 和 Flight 应存在哪里?
如果 LLD 题目只要求实现进程内的功能,可以先用内存集合保存数据。下面是一个简化的存储容器,User 和 Payment 使用各自的 ID 作为键:
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class InMemoryStore {
private final Map<Long, User> users = new HashMap<>();
private final Map<UUID, Payment> payments = new HashMap<>();
}
Flight 可以采用相同思路,其 ID 类型需要根据设计确定。随着逻辑增多,可以拆成 UserRepository、PaymentRepository 和 FlightRepository,分别提供保存和查询方法。
内存集合中的数据通常会随进程结束而丢失,也不会自动在多个进程间共享。如果要求重启后保留记录,就需要数据库等持久化存储,由 Repository 封装相应的读写操作。
HashMap 适合这里的基础示意。如果存在并发访问,还需要设计线程安全和业务操作的一致性,不能仅靠增加几个 Map 字段解决。
两个字段也值得检查
amount 当前使用 double。如果它表示需要精确十进制计算的金额,应考虑使用 BigDecimal,或使用表示最小货币单位的整数,并明确币种和精度规则。
isRefund 使用 Boolean,可以是 true、false 或 null。如果业务只允许两种状态,可以使用 boolean;如果 null 有明确含义,可以保留 Boolean。
备注:内容仅供参考。
