【问题标题】:How to avoid loading lazy bidirectional relationships with MOXy?如何避免使用 MOXy 加载惰性双向关系?
【发布时间】:2015-05-11 08:53:03
【问题描述】:

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

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

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

@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 表上的 SQL SELECT(按数字过滤),如果我使用 JOIN FETCH 查询,我可以获得关联的 @ 987654329@ 使用相同的单个 SELECT 语句。

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

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

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

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

【问题讨论】:

    标签: java jpa jaxb eclipselink moxy


    【解决方案1】:

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

    我总是在将模型实体传递到表示层之前分离它们,以便它们可以按照自己的意愿与模型类进行交互,而不会影响性能或数据库。

    【讨论】:

    • 感谢您的回复,但不幸的是这不起作用:就像我说的,我的实体已经从 EJB 以分离状态返回;我认为这是可以预料的,因为我使用了默认的持久性上下文和事务范围,而且我的 EJB 是无状态的。因此事务完成并伴随着相关的持久性上下文,分离所有实体。我在我的实体上使用 EntityManager.contains() 验证了这一点。我什至尝试在 EJB 方法中手动执行 EntityManager.clear(),但这没有任何改变。
    • 我不太明白,但在我看来,EclipseLink 内部用于惰性关系的IndirectList 在事务完成后仍然能够触发 SQL SELECT。这有意义吗?
    • 我刚刚发现一条评论支持我的猜测,here:“一般来说,EclipseLink 支持在持久性上下文关闭后访问 LAZY 关系。它使用其非事务性读取连接池来实现这一点。 "
    【解决方案2】:

    我从thesethreethreads收集了一些关于 EclipseLink 的信息。重要信息:

    Detached Objects 获取连接需要从 EntityManagerFactory 遍历 LAZY 关系,并且只要 EntityManagerFactory 处于打开状态就能够使用它。不是事务性连接中使用的连接,当您想在事务中使用实体时,必须正确合并它。

     

    这是 TopLink 实现的一个特殊功能,其中从非 tx 读取创建的分离实例仍然可以在其代理中访问以检索其他分离实例。如果对象是通过序列化分离的,这是不可能的。

     

    如果您希望 TopLink Essentials 在 EM 关闭后不处理惰性关系,我建议您在 GlassFish 中提交增强请求。

    但我找不到这样的增强请求,更不用说禁用此功能的实现可能性(根据具体情况)。

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

    1. 只是不要在同一个类上混合 JAXB 和 JPA 注释:改用一组不同的附加实例化的 JAXB 类,并在两个视图之间执行显式映射。如果从查询中返回大量实体,这可能会有点昂贵。

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

    3. 使用 JAXB Marshaller.Listener 排除所有未实例化的 IndirectContainer。

    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 );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-13
      • 2019-01-06
      • 2012-09-08
      • 1970-01-01
      • 1970-01-01
      • 2012-01-25
      • 2012-08-01
      • 2010-09-20
      相关资源
      最近更新 更多