JPA 实体关系获取最佳实践
JPA entity relation fetch best practice
我们目前正在尝试找出解决常见问题的最佳方案。
这是上下文:
上下文:
我们的项目是这样组成的
- 我们有
model
classes,确保传递的数据有效。
- 我们有
domain
classes,这些是带有 JPA 注释的简单 POJO。
用例
当请求到达 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();
}
限制
- 我们想保留
model
classes,因为这个应用需要多个程序通用API,所以我们想要实体模型。
- 我们不想将所有关系都作为
EAGER
加载。
问题
我们如何将数据从 domain
转换为 model
而不会遇到异常,这种情况下的最佳实践是什么。
我认为您可以使用 Open Session in View (or Transaction in View)
设计模式,您将在用户请求结束之前保持数据库连接打开。
当应用程序访问惰性集合时,Hibernate/JPA 将毫无问题地执行数据库查询,不会抛出任何异常。
希望对您有所帮助。
我最终决定选择:
- 仅 returns 对象的方法。
- returns 具有嵌套关联的对象的方法。 (使用 JPQL 请求)。
不是最好的解决方案,但我找不到另一个。
有一个名为 Blaze-Persistence Entity Views 的库。您仍然需要两种方法,但它们可以在必要时重用相同的查询逻辑,因为实体视图应用于现有查询。请注意,这也会提高性能,因为它只会获取您实际映射的数据。
我什至有一个关于你的确切用例的例子,一个 external model. By extending from the external model and passing data through a constructor mapping,你可以保持你的外部模型独立,同时获得良好的性能并避免使用 EAGER
.
我们目前正在尝试找出解决常见问题的最佳方案。 这是上下文:
上下文:
我们的项目是这样组成的
- 我们有
model
classes,确保传递的数据有效。 - 我们有
domain
classes,这些是带有 JPA 注释的简单 POJO。
用例
当请求到达 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();
}
限制
- 我们想保留
model
classes,因为这个应用需要多个程序通用API,所以我们想要实体模型。 - 我们不想将所有关系都作为
EAGER
加载。
问题
我们如何将数据从 domain
转换为 model
而不会遇到异常,这种情况下的最佳实践是什么。
我认为您可以使用 Open Session in View (or Transaction in View)
设计模式,您将在用户请求结束之前保持数据库连接打开。
当应用程序访问惰性集合时,Hibernate/JPA 将毫无问题地执行数据库查询,不会抛出任何异常。
希望对您有所帮助。
我最终决定选择:
- 仅 returns 对象的方法。
- returns 具有嵌套关联的对象的方法。 (使用 JPQL 请求)。
不是最好的解决方案,但我找不到另一个。
有一个名为 Blaze-Persistence Entity Views 的库。您仍然需要两种方法,但它们可以在必要时重用相同的查询逻辑,因为实体视图应用于现有查询。请注意,这也会提高性能,因为它只会获取您实际映射的数据。
我什至有一个关于你的确切用例的例子,一个 external model. By extending from the external model and passing data through a constructor mapping,你可以保持你的外部模型独立,同时获得良好的性能并避免使用 EAGER
.