如何避免使用 MOXy 加载惰性双向关系?

How to avoid loading lazy bidirectional relationships with MOXy?

我的问题是 this 评论的跟进。

我在同一个 class 上混合使用 JPA 和 JAXB (MOXy) 注释,大多数情况下都可以正常工作。如链接线程中所述,@XmlInverseReference 可防止编组双向关系时出现循环异常。但是为了检测循环,MOXy 必须检查链接实体的反向引用,如果需要填充惰性关系,这会导致额外的 SQL SELECTs。

为了详细说明问题,请考虑这个虚构的示例:

@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Phone {
    @ManyToOne
    @JoinColumn( name = "employeeID" )
    @XmlElement( name = "employee" )
    @XmlInverseReference( mappedBy = "phones" )
    private Employee employee;

    private String number;

    [...]
}


@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Employee {
    @OneToMany( mappedBy = "employee" )
    @XmlElementWrapper( name = "phones" )
    @XmlElement( name = "phone" )
    @XmlInverseReference( mappedBy = "employee" )
    private List<Phone> phones;

    private String name;

    [...]
}

现在我会 运行 使用这样的 JAX-RS 方法(使用底层 EJB)查询 Phones:

@Inject
private PhoneService phoneService;

@GET
@Path( "/phones" )
public List<Phone> getPhonesByNumber( @QueryParam( "number" ) String number ) {
    List<Phone> result = phoneService.getPhonesByNumber( number );

    return result;
}

发生了什么:PhoneService EJB 中的 JPQL 查询触发 Phone table 上的 SQL SELECT(按数字过滤),如果我使用 JOIN FETCH 查询,我可以使用相同的单个 SELECT 语句获取关联的 Employee

当 JAX-RS 方法 returns 时,JAXB 编组开始,这导致额外的 SQL SELECT:这个选择所有 Phones employeeID 指向与最初请求的 Phone 关联的 Employee。所以从EmployeePhone的惰性关系现在解决了,大概是因为MOXy必须能够判断原始的Phone是否包含在集合中。

我已经尝试使用 JPA 属性 访问和 JAXB 字段访问 phones 字段,如其他线程中所建议的那样,但无济于事。在从 EJB 检索结果后,我也尝试将链接 Employee 实例中的 phones 字段清空,即当我的实体已经分离时,但这导致立即 SQL再次 SELECT(似乎 EclipseLink 会在对 IndirectList 进行任何操作时执行此操作?)。我能找到的唯一解决方法是将 MOXy @XmlNamedObjectGraphs 与排除 phones 字段的子图一起使用。但这并不实际,尤其是当涉及的实体具有许多属性时。

因为我可能也需要从另一个方向查询,例如员工的姓名及其关联的电话,我不能只将 phones 标记为 @XmlTransient

有没有人有一个优雅的解决方案来抑制那些额外的 SQL 语句?

根据我的经验,完成您正在尝试的最简单的方法是在将所有实体 class 传递给 JAX-RS rest api 等表示层之前分离它们。您甚至可以使用 @OneToMany(mappedBy = "employee", cascade = CascadeType.DETACH)EntityManager.detach() 分离您的 phone class,然后分离您的员工 class,反之亦然。这将确保在您的实体编组期间,Jax-RS 不会触发您通常不想要的任何 SELECT 语句。

我总是在将模型实体传递到表示层之前分离模型实体,以便它们可以与模型交互class在不影响性能或数据库的情况下随心所欲地进行交互。

我从 these three threads 收集了一些关于 EclipseLink 的信息。重要位:

Detached Objects get the connection need to traverse the LAZY relationship from the EntityManagerFactory and will able able to use it as long as the EntityManagerFactory is open. The connection used in not the transactional one and when you want to use the entity in a transaction it will have to be properly merged.

This is a special feature of TopLink's implementation where the detached instances created from non-tx reads still have access in their proxies to retrieve additional dettached instances. If the object was detached through serialization this would not be possible.

If you would like TopLink Essentials to not process lazy relationships after the EM has closed I would recommend filing an enhancement request in GlassFish.

虽然我找不到这样的增强请求,更不用说禁用此功能的实施可能性(视具体情况而定)。

我可以想到五种可能的解决方法,每种都有自己的缺点:

  1. 只是不要在同一个 class 上混合使用 JAXB 和 JPA 注释:改用一组不同的额外实例化 JAXB classes 并在两者之间执行显式映射意见。如果从查询返回大量实体,这可能有点昂贵。

  2. 就像我在问题中提到的那样,使用 MOXy 的(命名的)对象图功能来排除(关系)字段被遍历。

  3. 使用 JAXB Marshaller.Listener 排除所有未实例化的间接容器。

  4. 由于序列化应该会破坏分离实体的此 EclipseLink 功能,因此请在编组它们之前对其进行序列化。虽然看起来很尴尬而且更贵。

  5. 这最接近模拟关闭该功能,但看起来也很老套:访问包装 IndirectContainer 及其包含的 ValueHolderInterface 并将它们设置为 null .示例代码:

(...)

import org.eclipse.persistence.indirection.IndirectContainer;

// entities must already be detached here, otherwise SQL UPDATEs will be triggered!
Employee e = phone.getEmployee();
IndirectContainer container = (IndirectContainer) e.getPhones();
container.setValueHolder( null );
e.setPhones( null );