【问题标题】:Detached JPA entities with associations in JSF backing beans与 JSF 支持 bean 中的关联分离的 JPA 实体
【发布时间】:2017-09-26 11:58:37
【问题描述】:

在我当前的项目中,我们使用 JSF 2.2、JPA 2(Hibernate 作为持久性提供者)和 Spring Data JPA。

情况如下,我尽量简化:我们有一个实体类CarExtra有双向关系,其中一个Car引用多个Extra实例。

public class Car {
    // ...

    @OneToMany(mappedBy = "car", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<Extra> extras;

    // ...
}

Extra 只包含一个String 属性和对Car 的反向引用。

在用于编辑单个汽车及其附加组件的支持 bean 中,我们希望将 bean 的状态保持在视图范围内(我们有一个自定义的 @ViewScope 基于 Spring 的注释,它有与 JEE 的 @ViewScoped 的行为相同)。

我们采用的方法基本上将Car 实例直接存储在支持bean 中。 CarRepository 是一个 Spring Data 存储库。

@Component
@ViewScope
public class CarEditView {

    @Getter @Setter
    private Integer id;

    @Autowired
    private CarRepository carRepository;

    @Getter
    private Car car;

    public void load() {
        car = carRepository.findOne(id);
    }

    public void save(){
        carRepository.save(car);
    }

    // ...

Car 实例直接引用并绑定在相关 *.xhtml 文件中的某些 JSF 相关标签中。

但是,Car 实例在第一次请求后被分离。现在让我们考虑在同一个视图中,有一种方法可以将Extra 实例添加到Car 实例。也许现有的也可以修改和删除。

在多个请求之间的同一页面上修改与其他实体有关系的分离 JPA 实体直到它们被显式保存的情况下,JSF 项目遵循的最佳实践是什么?

(请考虑extras是一个惰性集合,所以当这个集合没有被加载和访问时,比方说,在第二次请求中,会抛出异常。但是,保留新/修改/删除列表@ 987654339@ 实例在代码复杂度方面也感觉有点过分。)

【问题讨论】:

  • 对哪些开发者隐藏?如果您在“服务层”中进行所有合并,则 ui 开发人员永远不需要知道所有这些......
  • @Kukeltje 感谢您的评论。该问题的目标是与此类场景中的最佳实践相关的答案。最后一句话可能引向了另一个方向,所以我把它删掉了。
  • “最佳实践”问题在 stackoverflow 上并不是很好的问题,因为它们往往是固执己见的。但是imo ...“将其隐藏在服务中”。谷歌一点关于“扩展持久性”“在视图中打开会话”并阅读相关主题......
  • @Kukeltje 相信我,我已经阅读了很多博客文章并且我对 OSIV 模式非常熟悉(至少在 Hibernate 中,但在 Spring 中也有一个 OpenEntityManagerInViewFilter) .但是,与上面的示例相关,这里应该采取什么方法?我应该在支持 bean 中的哪里保留新的/修改的/删除的额外内容,是否真的有必要将这些 Extra 实例单独保留在一些帮助列表中,或者这甚至已经是扩展持久性上下文的用例?
  • 好的,那么下次请说明你们都发现了什么,为什么你认为它是好的(或不好的)。当前的问题太开放了。对相关信息等的引用太少(没有)......我个人只是在(帮助)列表中“存储/保留”所有新项目,并在实际需要保存时保存/合并所有项目。有时已经直接作为主/详细信息添加到另一个实体,有时不是。一切都取决于实际用例,

标签: jsf jpa spring-data-jpa jsf-2.2


【解决方案1】:

这种情况总是很难处理。我想您正在为您列出Cars 的情况懒惰地加载Extra 集合,而不是加载所有带有附加功能的汽车。你有很多解决方案:

  1. 使额外的集合被急切加载,并在显示列表时限制正在加载的汽车数量。这样一来,您的 bean 中将始终有额外的可用。

  2. 实现一个方法来返回每辆车的附加列表。通过这种方式,您可以删除与汽车实体本身的关系,并单独处理每个额外内容,对于视图,只需保留一个包含当前额外内容(可以有或没有 id)的集合,以及其他已删除的内容。保存版本时,调用您需要更新附加内容的服务方法。

如果您在服务中这样做,您甚至可以从视图中抽象出来(考虑到您总是保存带有附加功能的汽车):

@Transactional
public Car save(Car car, Collection<Extra> assignedExtras){
    Car result = carRepo.save(car);
    List<Extra> savedExtras = extraRepo.findByCar(car);
    for (Extra extra : assignedExtras){
        extra.setCar(car);
        extraRepo.save(extra);
        savedExtras.remove(extra);
    }
    //Here, savedExtras contains only the extras you have removed, so let's remove them
    for (Extra extra : savedExtras){
        extraRepo.delete(extra);
    }
    return result;
}
  1. 使用Entity Graphs,从 JPA 2.1 开始:

延迟加载通常是 JPA 2.0 的一个问题。如果要使用 FetchType.LAZY(默认)或 FetchType.EAGER 加载关系,则必须在实体处定义,并且始终使用此模式。 FetchType.EAGER 仅在我们希望始终加载关系时使用。 FetchType.LAZY 几乎在所有情况下都用于获得性能良好且可扩展的应用程序。 但这并非没有缺点。如果必须使用关系的元素,则需要确保关系在从数据库加载实体的事务中初始化。这可以通过使用从数据库中读取实体和所需关系的特定查询来完成。但这将导致特定于用例的查询。另一种选择是访问业务代码中的关系,这将导致对每个关系进行额外的查询。这两种方法都远非完美。

JPA 2.1 实体图是一个更好的解决方案。实体图的定义独立于查询,并定义从数据库中获取哪些属性。实体图可以用作获取图或加载图。如果使用 fetch 图,则只有实体图指定的属性将被视为 FetchType.EAGER。所有其他属性都将是惰性的。如果使用加载图,则实体图未指定的所有属性将保持其默认提取类型。

【讨论】:

  • 感谢您的评论。我担心答案会朝那个方向发展;-) 感谢您提到 JPA 2.1 实体图,这是我监督的事情。我们将通过 Spring Data 的 @EntityGraph 进行调查以找到一种可能使用渴望获取的方法。
猜你喜欢
  • 1970-01-01
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
相关资源
最近更新 更多