JPA实体关系获取最佳实践

时间:2015-12-25 00:46:30

标签: java jpa lazy-loading relationship dto

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

背景:

这就是我们项目的组成方式

  • 我们有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 loading。


问题

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

3 个答案:

答案 0 :(得分:1)

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

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

请参阅:Source 1Source 2

希望这有帮助。

答案 1 :(得分:0)

我最终决定创建:

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

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

答案 2 :(得分:0)

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

我甚至有一个关于您的确切用例的示例,external model。通过从外部模型扩展并通过constructor mapping传递数据,可以保持外部模型独立,同时获得良好的性能,避免使用EAGER

相关问题