与 JSF 支持 bean 中的关联分离的 JPA 实体

Detached JPA entities with associations in JSF backing beans

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

情况如下,我尽量简化了:我们有一个实体class 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 -based 注释,它与 JEE 的 @ViewScoped) 具有相同的行为。

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

@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 是一个惰性集合,所以当这个集合没有被加载和访问时,比方说,在第二个请求中,将抛出一个异常。但是,为 new/modified/deleted Extra 个实例在代码复杂度方面也感觉有点太多了。)

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

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

  2. 实现一个方法来 return 每辆车的附加列表。通过这种方式,您可以删除与汽车实体本身的关系,并且可以单独处理每个额外内容,对于视图,只需保留一个包含当前额外内容(可以有或没有 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 开始:

Lazy loading was often an issue with JPA 2.0. You have to define at the entity if you want to use FetchType.LAZY (default) or FetchType.EAGER to load the relation and this mode is always used. FetchType.EAGER is only used if we want to always load the relation. FetchType.LAZY is used in almost all of the cases to get a well performing and scalable application. But this is not without drawbacks. If you have to use an element of the relation, you need to make sure, that the relation gets initialized within the transaction that load the entity from the database. This can be done by using a specific query that reads the entity and the required relations from the database. But this will result in use case specific queries. Another option is to access the relation within your business code which will result in an additional query for each relation. Both approaches are far from perfect.

JPA 2.1 entity graphs are a better solution for it. The definition of an entity graph is independent of the query and defines which attributes to fetch from the database. An entity graph can be used as a fetch or a load graph. If a fetch graph is used, only the attributes specified by the entity graph will be treated as FetchType.EAGER. All other attributes will be lazy. If a load graph is used, all attributes that are not specified by the entity graph will keep their default fetch type.