【发布时间】:2026-02-02 07:25:02
【问题描述】:
我们目前正在尝试找出解决常见问题的最佳解决方案。 这是上下文:
上下文:
这就是我们项目的组成方式
- 我们有
model类,可确保传递的数据有效。 - 我们有
domain类,这些是带有 JPA 注释的简单 POJO。
用例
当请求到达 REST API 时,会收到一个格式为 model 对象的对象(作为 JSON)。
然后将该对象转换为要持久化的domain 对象。
最后将持久化的对象转换回model 对象以发送回视图。
问题
当我们将model 对象转换为domain 对象时,我们必须处理子对象。
但在某些情况下,model 对象没有加载子对象,然后我们会遇到 LazyLoading 异常。
具体例子:
model 类
public class Classroom {
private final String name;
private final RoomCapacity roomCapacity;
private final Set<RoomEquipment> equipments = new HashSet<>();
private Long id;
@JsonCreator
public Classroom(@JsonProperty("name") final String name, @JsonProperty("roomCapacity") final RoomCapacity roomCapacity) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("Cannot build a " + getClass().getName() + " without a name.");
}
if (roomCapacity == null) {
throw new IllegalArgumentException("Cannot build a " + getClass().getName() + " without a " + RoomCapacity.class.getName());
}
this.name = name;
this.roomCapacity = roomCapacity;
}
}
domain 类
@Entity
@Table(name = "classroom")
public class ClassroomDomain implements ModelTransformable<Classroom, Long> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "CLASSROOM_ID")
private Long id;
@Column(unique = true)
private String name;
@OneToMany(mappedBy = "primaryKey.classroom", cascade = CascadeType.REMOVE)
private Set<RoomEquipmentDomain> equipments = new HashSet<>();
private int capacity;
public ClassroomDomain(Classroom classroom) {
if (classroom == null) {
throw new IllegalArgumentException("Cannot instantiate a " + getClass().getName() + " with a null " + Classroom.class.getName());
}
id = classroom.getId();
name = classroom.getName();
capacity = classroom.getRoomCapacity().getMaxCapacity();
classroom.getEquipments().forEach(e -> equipments.add(new RoomEquipmentDomain(e, this)));
}
@Override
public Classroom toModel() {
Classroom classroom = new Classroom(name, new RoomCapacity(capacity));
classroom.setId(id);
equipments.forEach(e -> classroom.addEquipment(e.toModel()));
return classroom;
}
}
如您所见,domain 类有一个构造函数,它接受一个model 对象。并且domain 可以转换为模型。
因此,当我需要将 domain 转换为 model 时,它会失败,因为在某些情况下,我没有加载 equipments 列表,然后我遇到了 LazyLoading 异常。
当我在 DAO 中调用 toModel() 时它崩溃了。
public Classroom findOneById(Long id) {
if (id == null) {
throw new IllegalArgumentException("Cannot find a " + Classroom.class.getName() + " with a null id.");
}
ClassroomDomain domain = classroomRepository.findOne(id);
if (domain == null) {
throw new ClassroomNotFoundException("No " + Classroom.class.getName() + " found for id :" + id);
}
return domain.toModel();
}
限制
- 我们希望保留
model类,因为此应用程序需要是多个程序的通用 API,因此我们需要一个实体模型。 - 我们不希望将所有关系都作为
EAGER加载。
问题
我们如何在不遇到异常的情况下将数据从domain 转换为model,这种情况下的最佳做法是什么。
【问题讨论】:
-
不要从 DAO 返回模型(通常是 DTO:数据传输对象)。返回域类(称为实体)。您的业务逻辑应该与托管实体一起使用,并且实体到域的转换以及反之亦然在大多数情况下属于表示层。确保您的所有逻辑都在事务内部运行。让 EM 在整个事务期间保持打开状态,以便延迟加载工作。使用 Java EE 容器或 Spring 以声明方式为您管理 EM 和事务。
-
嗨,@JBNizet 我确实使用弹簧,但我不想使用事务,因为:ClassroomDomain.toModel() 调用 Classroom.getEquipments.toModel(),所以我不每次都想加载整个数据库。该视图实际上是一个角度应用程序,因此:/ 无法访问事务。最后最大的问题是
model类的结构与domain不同,因为@Embedded实体。 -
这没有多大意义。为什么它调用 Classroom.getEquipments.toModel() 是您不想损坏设备吗?为什么你认为加载设备会加载整个数据库?这如何阻止您使用交易?如果您不想加载东西,请不要加载它们。如果你想加载东西,然后加载它们。不使用事务不是避免加载东西的方法。
-
重点是:当请求对象时(比如说id 为2 的教室),我们得到一个
domain对象,但其余API 应该返回一个model对象。在某些情况下,domain对象会加载设备,但有时不会。我不想要交易,因为当我加载包含教室的对象时,.toModelt()将加载所有教室,然后所有 classromm 将加载他们所有的设备,依此类推。 -
你不明白什么是交易。交易不会像您声称的那样自动加载所有内容。 您的代码 就是这样做的。通过执行
equipments.forEach(e -> classroom.addEquipment(e.toModel()));,你要求JPA加载教室的设备。它失败了,因为持久性上下文已关闭。使用事务并打开持久性上下文只会使代码做它应该做的事情:成功加载设备。为了避免装载设备,解决方案不是避免交易。解决方案是避免执行该代码。
标签: java jpa lazy-loading relationship dto