JPA 实体关系获取最佳实践

JPA entity relation fetch best practice

我们目前正在尝试找出解决常见问题的最佳方案。 这是上下文:

上下文:

我们的项目是这样组成的


用例

当请求到达 REST API 时,会收到格式为 model 对象(如 JSON)的对象。
然后这个对象被转换为一个 domain 对象被持久化。 最后,持久对象被转换回 model 对象以发送回视图。


问题

当我们将 model 对象转换为 domain 对象时,我们必须处理子对象。
但是在某些情况下 model 对象没有加载子对象,然后我们会遇到 LazyLoading 异常。


具体例子:

model class

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 class

@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 class 有一个接受 model 对象的构造函数。而domain可以转化为model.

因此,当我需要将 domain 转换为 model 时,它失败了,因为在某些情况下,我没有加载 equipments 列表,然后我遇到了延迟加载异常。

当我在 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();
    }


限制


问题

我们如何将数据从 domain 转换为 model 而不会遇到异常,这种情况下的最佳实践是什么。

我认为您可以使用 Open Session in View (or Transaction in View) 设计模式,您将在用户请求结束之前保持数据库连接打开。

当应用程序访问惰性集合时,Hibernate/JPA 将毫无问题地执行数据库查询,不会抛出任何异常。

请参考:Source 1, Source 2

希望对您有所帮助。

我最终决定选择:

  • 仅 returns 对象的方法。
  • returns 具有嵌套关联的对象的方法。 (使用 JPQL 请求)。

不是最好的解决方案,但我找不到另一个。

有一个名为 Blaze-Persistence Entity Views 的库。您仍然需要两种方法,但它们可以在必要时重用相同的查询逻辑,因为实体视图应用于现有查询。请注意,这也会提高性能,因为它只会获取您实际映射的数据。

我什至有一个关于你的确切用例的例子,一个 external model. By extending from the external model and passing data through a constructor mapping,你可以保持你的外部模型独立,同时获得良好的性能并避免使用 EAGER.